feat(twap-monitor): workspace + skeleton (BLEU-825) - #2
Closed
brunota20 wants to merge 1 commit into
Closed
Conversation
Add modules/twap-monitor/ as a workspace member. Cargo.toml declares [lib] crate-type = ["cdylib"] for WASM Component output, and pulls the deps the TWAP module path needs: cowprotocol (default-features off — only typed primitives and OrderCreation surface needed), alloy-sol-types (event/return decoding lands in BLEU-826/827), and wit-bindgen. src/lib.rs binds against the shepherd:cow/shepherd world (event- module imports + cow-api). generate_all is required because the world include pulls nexum:host/types across packages — without it, wit-bindgen panics on the missing cross-package mapping. init and on_event are stubbed: init logs once; on_event is a no-op until the Event::Log / Event::Block dispatch lands in BLEU-826 / BLEU-827. Verification: cargo build --target wasm32-wasip2 --release -p twap-monitor emits a 65 KB .wasm. Engine load is gated on module.toml (BLEU-834).
This was referenced Jun 15, 2026
Author
|
Work landed via |
brunota20
added a commit
that referenced
this pull request
Jun 25, 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.
brunota20
added a commit
that referenced
this pull request
Jun 25, 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.
brunota20
added a commit
that referenced
this pull request
Jun 25, 2026
The supervisor's dispatch path is per-chain by construction (`dispatch_block(block)` filters modules by `block.chain_id` matching their `[[subscription]]` table), and the COW-1071 WS reconnect tasks own one per-chain backoff timer each. Multi-chain isolation is therefore structural, not derived. This PR locks the guarantee into the test suite with two new integration tests + a supervisor.rs docstring stating the invariant explicitly. ## New tests `multi_chain_dispatch_isolates_modules_by_chain`: - Boot two `example` modules with different `[[subscription]]` chain_ids (1 + 100). - Dispatch a block on chain 1 -> only module-a receives it (dispatched=1, alive_count=2 unchanged). - Dispatch a block on chain 100 -> only module-b receives it. - Validates: subscription filter is per-chain; a block on one chain does not even enter modules subscribed to a different chain. `multi_chain_poisoned_module_does_not_affect_other_chains`: - Boot fuel-bomb (always-traps) on chain 1 + example (healthy) on chain 100, with `PoisonPolicy::new(2, 60s)`. - Trap bomb #1 on chain 1 -> bomb dies, poisoned=0, example untouched. - Dispatch on chain 100 -> example receives (1/1). - Wait 1.1 s (bomb backoff window), trap bomb #2 -> poisoned=1. - Dispatch on chain 100 again -> example STILL receives. - Validates: a permanently-poisoned module on one chain does not consume restart slots, fuel, or scheduling attention from modules on any other chain. Total wall-clock ~1.2 s for the second test (one backoff window). ## supervisor.rs docstring The module-level comment now articulates the multi-chain isolation invariant explicitly so a future reader of the dispatch path knows the property is load-bearing. ## What this proves Supervisor side (dispatch fast-path): - Per-module `alive`, `failure_count`, `next_attempt`, `poisoned` are independent of which chain triggered the event. - Subscription filter excludes mismatched modules before any dispatch / restart logic runs. Upstream side (already proven by COW-1071's architecture): - `open_block_streams` spawns one task per chain; tasks share no state. A chain-A WS drop changes only chain-A's task state. - `open_log_streams` is per-(module, chain) -> even tighter isolation than block streams. ## Out of scope - A unit test that "fakes a WS drop" on chain A while chain B keeps yielding. Requires mocking `ProviderPool::subscribe_blocks` which today goes through real alloy / tokio infrastructure. The COW-1064 (E2E 4-6h testnet) and COW-1031 (7-day soak) will exercise this path against live RPCs. - Per-chain configurable backoff / health-window. Today the reconnect policy is workspace-wide; per-chain tuning is a 0.3 follow-up. ## Workspace impact - `cargo test --workspace` -> 163 host tests + 6 doctests passing (was 161 + 6; +2 from the new integration tests). - `cargo clippy --all-targets --workspace -- -D warnings` clean. - `cargo fmt --all --check` clean. Linear: COW-1073. Ninth M4 issue landed; stacks on #42 (COW-1072).
brunota20
added a commit
that referenced
this pull request
Jun 25, 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.
brunota20
added a commit
that referenced
this pull request
Jun 25, 2026
The supervisor's dispatch path is per-chain by construction (`dispatch_block(block)` filters modules by `block.chain_id` matching their `[[subscription]]` table), and the COW-1071 WS reconnect tasks own one per-chain backoff timer each. Multi-chain isolation is therefore structural, not derived. This PR locks the guarantee into the test suite with two new integration tests + a supervisor.rs docstring stating the invariant explicitly. ## New tests `multi_chain_dispatch_isolates_modules_by_chain`: - Boot two `example` modules with different `[[subscription]]` chain_ids (1 + 100). - Dispatch a block on chain 1 -> only module-a receives it (dispatched=1, alive_count=2 unchanged). - Dispatch a block on chain 100 -> only module-b receives it. - Validates: subscription filter is per-chain; a block on one chain does not even enter modules subscribed to a different chain. `multi_chain_poisoned_module_does_not_affect_other_chains`: - Boot fuel-bomb (always-traps) on chain 1 + example (healthy) on chain 100, with `PoisonPolicy::new(2, 60s)`. - Trap bomb #1 on chain 1 -> bomb dies, poisoned=0, example untouched. - Dispatch on chain 100 -> example receives (1/1). - Wait 1.1 s (bomb backoff window), trap bomb #2 -> poisoned=1. - Dispatch on chain 100 again -> example STILL receives. - Validates: a permanently-poisoned module on one chain does not consume restart slots, fuel, or scheduling attention from modules on any other chain. Total wall-clock ~1.2 s for the second test (one backoff window). ## supervisor.rs docstring The module-level comment now articulates the multi-chain isolation invariant explicitly so a future reader of the dispatch path knows the property is load-bearing. ## What this proves Supervisor side (dispatch fast-path): - Per-module `alive`, `failure_count`, `next_attempt`, `poisoned` are independent of which chain triggered the event. - Subscription filter excludes mismatched modules before any dispatch / restart logic runs. Upstream side (already proven by COW-1071's architecture): - `open_block_streams` spawns one task per chain; tasks share no state. A chain-A WS drop changes only chain-A's task state. - `open_log_streams` is per-(module, chain) -> even tighter isolation than block streams. ## Out of scope - A unit test that "fakes a WS drop" on chain A while chain B keeps yielding. Requires mocking `ProviderPool::subscribe_blocks` which today goes through real alloy / tokio infrastructure. The COW-1064 (E2E 4-6h testnet) and COW-1031 (7-day soak) will exercise this path against live RPCs. - Per-chain configurable backoff / health-window. Today the reconnect policy is workspace-wide; per-chain tuning is a 0.3 follow-up. ## Workspace impact - `cargo test --workspace` -> 163 host tests + 6 doctests passing (was 161 + 6; +2 from the new integration tests). - `cargo clippy --all-targets --workspace -- -D warnings` clean. - `cargo fmt --all --check` clean. Linear: COW-1073. Ninth M4 issue landed; stacks on #42 (COW-1072).
brunota20
added a commit
that referenced
this pull request
Jun 25, 2026
Audit reference: milestone-rubric-grant-audit-2026-06-25.md, Major #1. Vendored rubric mandates `strum::IntoStaticStr` (with `#[strum(serialize_all = "snake_case")]`) on every error enum so `error_kind` labels on the `shepherd_chain_request_total` / `shepherd_cow_api_*` counters stay in lock-step with the Rust source of truth instead of growing a `match err { ... => "connect" ... }` ladder per call site. Enums covered on this milestone (the ones present on dev/m2-base): - `nexum_engine::host::cow_orderbook::CowApiError` - `nexum_engine::host::provider_pool::ProviderError` - `nexum_engine::manifest::error::ParseError` - `nexum_engine::engine_config::EngineConfigError` Also adds `#[non_exhaustive]` to `CowApiError` and `ProviderError` (audit Major #2). The other two already carried it. `strum = "0.26"` lands as a direct dep on nexum-engine. The workspace-deps hoist (audit P1, Major #5) is intentionally a separate judgment call left to Bruno; this commit ships the substantive rubric fix without coupling to the broader Cargo.toml restructure.
brunota20
added a commit
that referenced
this pull request
Jun 25, 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.
brunota20
added a commit
that referenced
this pull request
Jun 25, 2026
…host/cow enums Audit reference: milestone-rubric-grant-audit-2026-06-25.md, Major #1 and Major #2. The rubric mandates `strum::IntoStaticStr` on every error / event enum (snake_case variant names land as `&'static str` for metric labels and structured-log `error_kind` fields) and `#[non_exhaustive]` on any public enum that may grow variants. Two enums on the SDK side: - `shepherd_sdk::host::HostErrorKind`: adds both attributes (previously carried neither; M5 audit table is wrong - the M5 tip lacked non_exhaustive on m3-base specifically because the m4 cherry-pick added it. Landing on m3-base instead so the SDK ships with it). - `shepherd_sdk::cow::error::RetryAction`: already had non_exhaustive; derive adds the IntoStaticStr. The `bind_host_via_wit_bindgen!` macro gains a `_ => Internal` wildcard in its SDK -> wit-bindgen HostErrorKind remap so module crates compile against future variants without source changes (the same change M5 already carries; landing here on the SDK home milestone keeps the SDK self-contained). `strum = "0.26"` is a `default-features = false` SDK dep.
brunota20
added a commit
that referenced
this pull request
Jun 25, 2026
…BuildError Audit reference: milestone-rubric-grant-audit-2026-06-25.md, Major #1 and Major #2 (BuildError listed for both treatments). `BuildError` is the cowprotocol-side rejection envelope returned when `build_order_creation` cannot assemble an `OrderCreation` body. The submission-failure warn log can now carry `error_kind = unknown_marker | cowprotocol` directly off the enum instead of growing a `match err { ... => "unknown_marker" ... }` ladder in the call site. `strum = "0.26"` (default-features = false) lands as a direct dep of the twap-monitor module. The enum is `enum BuildError` (not `pub`) but adding `non_exhaustive` is still the rubric default for error enums: it documents intent and costs nothing at the single call site inside this module.
brunota20
added a commit
that referenced
this pull request
Jun 25, 2026
The supervisor's dispatch path is per-chain by construction (`dispatch_block(block)` filters modules by `block.chain_id` matching their `[[subscription]]` table), and the COW-1071 WS reconnect tasks own one per-chain backoff timer each. Multi-chain isolation is therefore structural, not derived. This PR locks the guarantee into the test suite with two new integration tests + a supervisor.rs docstring stating the invariant explicitly. ## New tests `multi_chain_dispatch_isolates_modules_by_chain`: - Boot two `example` modules with different `[[subscription]]` chain_ids (1 + 100). - Dispatch a block on chain 1 -> only module-a receives it (dispatched=1, alive_count=2 unchanged). - Dispatch a block on chain 100 -> only module-b receives it. - Validates: subscription filter is per-chain; a block on one chain does not even enter modules subscribed to a different chain. `multi_chain_poisoned_module_does_not_affect_other_chains`: - Boot fuel-bomb (always-traps) on chain 1 + example (healthy) on chain 100, with `PoisonPolicy::new(2, 60s)`. - Trap bomb #1 on chain 1 -> bomb dies, poisoned=0, example untouched. - Dispatch on chain 100 -> example receives (1/1). - Wait 1.1 s (bomb backoff window), trap bomb #2 -> poisoned=1. - Dispatch on chain 100 again -> example STILL receives. - Validates: a permanently-poisoned module on one chain does not consume restart slots, fuel, or scheduling attention from modules on any other chain. Total wall-clock ~1.2 s for the second test (one backoff window). ## supervisor.rs docstring The module-level comment now articulates the multi-chain isolation invariant explicitly so a future reader of the dispatch path knows the property is load-bearing. ## What this proves Supervisor side (dispatch fast-path): - Per-module `alive`, `failure_count`, `next_attempt`, `poisoned` are independent of which chain triggered the event. - Subscription filter excludes mismatched modules before any dispatch / restart logic runs. Upstream side (already proven by COW-1071's architecture): - `open_block_streams` spawns one task per chain; tasks share no state. A chain-A WS drop changes only chain-A's task state. - `open_log_streams` is per-(module, chain) -> even tighter isolation than block streams. ## Out of scope - A unit test that "fakes a WS drop" on chain A while chain B keeps yielding. Requires mocking `ProviderPool::subscribe_blocks` which today goes through real alloy / tokio infrastructure. The COW-1064 (E2E 4-6h testnet) and COW-1031 (7-day soak) will exercise this path against live RPCs. - Per-chain configurable backoff / health-window. Today the reconnect policy is workspace-wide; per-chain tuning is a 0.3 follow-up. ## Workspace impact - `cargo test --workspace` -> 163 host tests + 6 doctests passing (was 161 + 6; +2 from the new integration tests). - `cargo clippy --all-targets --workspace -- -D warnings` clean. - `cargo fmt --all --check` clean. Linear: COW-1073. Ninth M4 issue landed; stacks on #42 (COW-1072).
brunota20
added a commit
that referenced
this pull request
Jun 25, 2026
Audit reference: milestone-rubric-grant-audit-2026-06-25.md, Major #1. Vendored rubric mandates `strum::IntoStaticStr` (with `#[strum(serialize_all = "snake_case")]`) on every error enum so `error_kind` labels on the `shepherd_chain_request_total` / `shepherd_cow_api_*` counters stay in lock-step with the Rust source of truth instead of growing a `match err { ... => "connect" ... }` ladder per call site. Enums covered on this milestone (the ones present on dev/m2-base): - `nexum_engine::host::cow_orderbook::CowApiError` - `nexum_engine::host::provider_pool::ProviderError` - `nexum_engine::manifest::error::ParseError` - `nexum_engine::engine_config::EngineConfigError` Also adds `#[non_exhaustive]` to `CowApiError` and `ProviderError` (audit Major #2). The other two already carried it. `strum = "0.26"` lands as a direct dep on nexum-engine. The workspace-deps hoist (audit P1, Major #5) is intentionally a separate judgment call left to Bruno; this commit ships the substantive rubric fix without coupling to the broader Cargo.toml restructure.
brunota20
added a commit
that referenced
this pull request
Jun 25, 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.
brunota20
added a commit
that referenced
this pull request
Jun 25, 2026
…host/cow enums Audit reference: milestone-rubric-grant-audit-2026-06-25.md, Major #1 and Major #2. The rubric mandates `strum::IntoStaticStr` on every error / event enum (snake_case variant names land as `&'static str` for metric labels and structured-log `error_kind` fields) and `#[non_exhaustive]` on any public enum that may grow variants. Two enums on the SDK side: - `shepherd_sdk::host::HostErrorKind`: adds both attributes (previously carried neither; M5 audit table is wrong - the M5 tip lacked non_exhaustive on m3-base specifically because the m4 cherry-pick added it. Landing on m3-base instead so the SDK ships with it). - `shepherd_sdk::cow::error::RetryAction`: already had non_exhaustive; derive adds the IntoStaticStr. The `bind_host_via_wit_bindgen!` macro gains a `_ => Internal` wildcard in its SDK -> wit-bindgen HostErrorKind remap so module crates compile against future variants without source changes (the same change M5 already carries; landing here on the SDK home milestone keeps the SDK self-contained). `strum = "0.26"` is a `default-features = false` SDK dep.
brunota20
added a commit
that referenced
this pull request
Jun 25, 2026
…BuildError Audit reference: milestone-rubric-grant-audit-2026-06-25.md, Major #1 and Major #2 (BuildError listed for both treatments). `BuildError` is the cowprotocol-side rejection envelope returned when `build_order_creation` cannot assemble an `OrderCreation` body. The submission-failure warn log can now carry `error_kind = unknown_marker | cowprotocol` directly off the enum instead of growing a `match err { ... => "unknown_marker" ... }` ladder in the call site. `strum = "0.26"` (default-features = false) lands as a direct dep of the twap-monitor module. The enum is `enum BuildError` (not `pub`) but adding `non_exhaustive` is still the rubric default for error enums: it documents intent and costs nothing at the single call site inside this module.
brunota20
added a commit
that referenced
this pull request
Jun 25, 2026
The supervisor's dispatch path is per-chain by construction (`dispatch_block(block)` filters modules by `block.chain_id` matching their `[[subscription]]` table), and the COW-1071 WS reconnect tasks own one per-chain backoff timer each. Multi-chain isolation is therefore structural, not derived. This PR locks the guarantee into the test suite with two new integration tests + a supervisor.rs docstring stating the invariant explicitly. ## New tests `multi_chain_dispatch_isolates_modules_by_chain`: - Boot two `example` modules with different `[[subscription]]` chain_ids (1 + 100). - Dispatch a block on chain 1 -> only module-a receives it (dispatched=1, alive_count=2 unchanged). - Dispatch a block on chain 100 -> only module-b receives it. - Validates: subscription filter is per-chain; a block on one chain does not even enter modules subscribed to a different chain. `multi_chain_poisoned_module_does_not_affect_other_chains`: - Boot fuel-bomb (always-traps) on chain 1 + example (healthy) on chain 100, with `PoisonPolicy::new(2, 60s)`. - Trap bomb #1 on chain 1 -> bomb dies, poisoned=0, example untouched. - Dispatch on chain 100 -> example receives (1/1). - Wait 1.1 s (bomb backoff window), trap bomb #2 -> poisoned=1. - Dispatch on chain 100 again -> example STILL receives. - Validates: a permanently-poisoned module on one chain does not consume restart slots, fuel, or scheduling attention from modules on any other chain. Total wall-clock ~1.2 s for the second test (one backoff window). ## supervisor.rs docstring The module-level comment now articulates the multi-chain isolation invariant explicitly so a future reader of the dispatch path knows the property is load-bearing. ## What this proves Supervisor side (dispatch fast-path): - Per-module `alive`, `failure_count`, `next_attempt`, `poisoned` are independent of which chain triggered the event. - Subscription filter excludes mismatched modules before any dispatch / restart logic runs. Upstream side (already proven by COW-1071's architecture): - `open_block_streams` spawns one task per chain; tasks share no state. A chain-A WS drop changes only chain-A's task state. - `open_log_streams` is per-(module, chain) -> even tighter isolation than block streams. ## Out of scope - A unit test that "fakes a WS drop" on chain A while chain B keeps yielding. Requires mocking `ProviderPool::subscribe_blocks` which today goes through real alloy / tokio infrastructure. The COW-1064 (E2E 4-6h testnet) and COW-1031 (7-day soak) will exercise this path against live RPCs. - Per-chain configurable backoff / health-window. Today the reconnect policy is workspace-wide; per-chain tuning is a 0.3 follow-up. ## Workspace impact - `cargo test --workspace` -> 163 host tests + 6 doctests passing (was 161 + 6; +2 from the new integration tests). - `cargo clippy --all-targets --workspace -- -D warnings` clean. - `cargo fmt --all --check` clean. Linear: COW-1073. Ninth M4 issue landed; stacks on #42 (COW-1072).
jean-neiverth
pushed a commit
that referenced
this pull request
Jun 29, 2026
Audit reference: milestone-rubric-grant-audit-2026-06-25.md, Major #1. Vendored rubric mandates `strum::IntoStaticStr` (with `#[strum(serialize_all = "snake_case")]`) on every error enum so `error_kind` labels on the `shepherd_chain_request_total` / `shepherd_cow_api_*` counters stay in lock-step with the Rust source of truth instead of growing a `match err { ... => "connect" ... }` ladder per call site. Enums covered on this milestone (the ones present on dev/m2-base): - `nexum_engine::host::cow_orderbook::CowApiError` - `nexum_engine::host::provider_pool::ProviderError` - `nexum_engine::manifest::error::ParseError` - `nexum_engine::engine_config::EngineConfigError` Also adds `#[non_exhaustive]` to `CowApiError` and `ProviderError` (audit Major #2). The other two already carried it. `strum = "0.26"` lands as a direct dep on nexum-engine. The workspace-deps hoist (audit P1, Major #5) is intentionally a separate judgment call left to Bruno; this commit ships the substantive rubric fix without coupling to the broader Cargo.toml restructure.
jean-neiverth
pushed a commit
that referenced
this pull request
Jun 30, 2026
Audit reference: milestone-rubric-grant-audit-2026-06-25.md, Major #1. Vendored rubric mandates `strum::IntoStaticStr` (with `#[strum(serialize_all = "snake_case")]`) on every error enum so `error_kind` labels on the `shepherd_chain_request_total` / `shepherd_cow_api_*` counters stay in lock-step with the Rust source of truth instead of growing a `match err { ... => "connect" ... }` ladder per call site. Enums covered on this milestone (the ones present on dev/m2-base): - `nexum_engine::host::cow_orderbook::CowApiError` - `nexum_engine::host::provider_pool::ProviderError` - `nexum_engine::manifest::error::ParseError` - `nexum_engine::engine_config::EngineConfigError` Also adds `#[non_exhaustive]` to `CowApiError` and `ProviderError` (audit Major #2). The other two already carried it. `strum = "0.26"` lands as a direct dep on nexum-engine. The workspace-deps hoist (audit P1, Major #5) is intentionally a separate judgment call left to Bruno; this commit ships the substantive rubric fix without coupling to the broader Cargo.toml restructure.
jean-neiverth
pushed a commit
that referenced
this pull request
Jun 30, 2026
Audit reference: milestone-rubric-grant-audit-2026-06-25.md, Major #1. Vendored rubric mandates `strum::IntoStaticStr` (with `#[strum(serialize_all = "snake_case")]`) on every error enum so `error_kind` labels on the `shepherd_chain_request_total` / `shepherd_cow_api_*` counters stay in lock-step with the Rust source of truth instead of growing a `match err { ... => "connect" ... }` ladder per call site. Enums covered on this milestone (the ones present on dev/m2-base): - `nexum_engine::host::cow_orderbook::CowApiError` - `nexum_engine::host::provider_pool::ProviderError` - `nexum_engine::manifest::error::ParseError` - `nexum_engine::engine_config::EngineConfigError` Also adds `#[non_exhaustive]` to `CowApiError` and `ProviderError` (audit Major #2). The other two already carried it. `strum = "0.26"` lands as a direct dep on nexum-engine. The workspace-deps hoist (audit P1, Major #5) is intentionally a separate judgment call left to Bruno; this commit ships the substantive rubric fix without coupling to the broader Cargo.toml restructure.
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.
Summary
modules/twap-monitor/as a workspace member with[lib] crate-type = [\"cdylib\"]for WASM Component output.cowprotocol(default-features off),alloy-sol-types,wit-bindgen— pre-pulled so BLEU-826/827/828 can layer in event decoding and submission without churning Cargo.toml.src/lib.rsbinds againstshepherd:cow/shepherdworld;initlogs once,on_eventis a no-op stub until BLEU-826 (TWAP indexing) and BLEU-827 (poll path) land.generate_allis required because the shepherd world include pullsnexum:host/typesacross packages — without it,wit_bindgen::generate!panics on the missing cross-package mapping.Base:
dev/m2-base(carries the in-flight nullislabs/shepherd PRs #8/#9/#12/#15 so M2 can build on top of them while upstream review continues).Linear: BLEU-825.
Test plan