Conversation
* feat(sdk): add the keeper conditional-source seam, retry ledger, and run Introduce the world-neutral half of the poll loop in nexum_sdk::keeper: a ConditionalSource trait (one watch in, one outcome out, generic over the host, no venue-transport pre-abstraction), a Tick carrying the dispatch instant, and the RetryAction (try-next-block / backoff / drop) whose effects the Retrier runs through the gate and watch-set stores. RetryAction moves out of shepherd-sdk; the CoW crate re-exports it so no module rewires. shepherd-sdk keeps classify_api_error as the CoW errorType table but returns the keeper action, and encodes two fixes in the one classification path: classify_poll_error maps an unrecognised revert selector with a structured payload to DontTryAgain instead of re-polling every block forever, and DuplicatedOrder (both spellings) classifies as already-submitted - the run records the submitted: receipt and keeps the watch rather than dropping every future tranche. classify_submit_error widens the table to the whole CowApiError surface and gives Backoff its producer: a rate-limit fault with server guidance gates the watch on the epoch clock. cow::run composes the loop: watch-set scan -> gate check -> source poll -> PollOutcome dispatch, driving submission through the existing CowApiHost seam with the journal as the idempotency guard and the retry ledger as the failure dispatch. Acceptance tests run against the composed shepherd-sdk-test MockHost as integration tests; no module is rewired yet. * docs(sdk): disambiguate the run intra-doc link * fix(cow): build the classify_poll_error test RpcError data as Bytes RpcError.data is now Bytes; the test helper takes raw Vec<u8> and wraps it (Vec -> Bytes is O(1)). * fix(cow): keep the sweep alive when a post-submit journal write faults journal.record after a successful submit_order was propagating a store fault out of submit_ready, aborting the whole watch sweep with the receipt unwritten - the next tick then re-polled and re-posted the same order. Log the failure and carry on instead; the already-submitted arm keeps the re-post idempotent. Both post-submit writes are covered: the Ok arm and the already-submitted (DuplicatedOrder) arm.
* feat(sdk): log run diagnostics through the tracing facade The run logged through the LoggingHost seam, which the guest tracing capture cannot observe - module tests proving behaviour identity for keeper ports assert on tracing events. Route the submit-path diagnostics through the tracing macros with the wording the legacy twap poll loop established, and give ConditionalSource a defaulted label so shared log lines attribute the strategy that produced them. * refactor(twap-monitor): port the poll loop onto the keeper composition Reduce strategy.rs to decode and evaluate: log decoding persists watches through the keeper watch set, and the getTradeableOrderWithSignature evaluation moves behind ConditionalSource. The hand-rolled gate keys, watch-update lifecycle, submitted: markers, and submit-retry dispatch are deleted in favour of cow::run over the keeper stores and retry ledger, still on the legacy CowApiHost seam. The MockHost dispatch tests are untouched - the behaviour-identity proof for the port.
* feat(sdk-test): namespace the mock local store and share its limits Rework MockLocalStore to mirror the runtime store's shape: namespaced views over one shared row map, so identical key strings in sibling namespaces never collide, plus store-wide entry and byte limits that span namespaces the way one database file does. Fault injection stays per-view. * feat(sdk-test): add the programmable MockVenue on the cow-api seam Script per-call venue behaviour where MockCowApi replays one response: a strictly-draining queue of submit outcomes with a configurable steady-state fallback, per-route response sequences whose final entry sticks (a terminal order status persists across re-polls), and venue fault injection that overrides both without consuming them. MockHost grows a defaulted venue type parameter so the composed host carries either mock on the same CowApiHost seam. Acceptance tests drive the keeper run across multi-tick retry, backoff, and outage scenarios, and module-shaped strategy code against status sequences.
* fix(sdk): drop the watch when the OrderCreation constructor rejects the order A constructor rejection (zero from, validTo beyond the client-side one-year horizon) is deterministic for the polled payload: skipping with the watch intact re-polled and re-warned on every block forever. Route it through the retry ledger as a drop, matching the pre-keeper net effect where the orderbook rejected the shipped body (e.g. ExcessiveValidTo) and the classifier dropped the watch. The unknown-enum-marker skip keeps its pinned keep-the-watch behaviour. Also log the keeper-level DontTryAgain removal so a permanent drop is observable even for sources that do not log their own outcomes. * fix(stop-loss): record the submitted receipt on a duplicate rejection classify_api_error maps DuplicatedOrder to TryNextBlock on the premise that the caller records the submitted: receipt - true for the run, but stop-loss consumed the classification without the compensating write, so a duplicate rejection re-POSTed every block until validTo. Mirror run::submit_ready: treat already-submitted as success wearing an error status, write the marker the dedup guard reads, and idle. * refactor(sdk): hoist the watch key to a free fn and shed the cross-layer dev cycle WatchSet::key never used the host parameter, forcing a meaningless turbofish at every call site (one test existed solely to prove two instantiations agree). keeper::watch_key is the free canon; WatchSet::key stays as a thin delegate for discoverability. The keeper acceptance tests only touch the local-store seam, so they now run against nexum-sdk-test's MockHost (the same MockLocalStore type), shrinking nexum-sdk's dev cycle from the cross-layer shepherd-sdk-test pair - which dragged the whole CoW domain layer into the world-neutral crate's dev graph - to the within-layer pair its own mock crate documents. * feat(twap-monitor): carry the revert cause on the permanent poll-drop path A revert that classifies to DontTryAgain destroyed the watch with only an Info outcome label; the selector and node message needed to triage a wrongly-dropped watch were discarded. Warn with both before returning the outcome, and pin the log lines in the drop test. The test-only watch_key wrapper now reuses the keeper free fn. * docs(sdk): correct stale order.rs rustdoc OrderCreation::from_signed_order_data was renamed to OrderCreation::new in cowprotocol 0.2; and only the unknown-marker case of order_uid_hex returning None also stops the submit path downstream - an unsupported chain id does not, so say so instead of claiming both do. * chore(backtest): retire the dead app_data resolution scripting The epic removed resolve_app_data and the orderbook-mock's GET /api/v1/app_data route, but the replay harness still programmed that route against a strategy that never requests it. Drop the dead programming (and the now-unused shepherd-sdk dependency) and refresh the module doc to the hash-only submission flow. The RejectedExpected classification is kept for historical report comparability until the harness is reworked around the observer-only strategy (follow-up). * docs: surface the keeper and run in the overview; record the cow-rs patch-channel move The overview's SDK table predated the epic's headline surface: add the nexum-sdk keeper row and the shepherd-sdk cow::run row. Amend ADR-0004 with the move of the [patch.crates-io] channel from bleu/cow-rs to nullislabs/cow-rs and what the pinned rev carries. * style: rustfmt the sweep additions * docs: reword PR-number references out of module comments Numeric issue/PR references in .rs files go stale across repository moves; describe the change instead of pointing at the review.
* feat(sdk): scaffold nexum-macros and ship #[module] attribute Add the `nexum-macros` proc-macro crate. `#[nexum_sdk::module]` generates the per-cdylib glue every module hand-wrote: the `wit_bindgen::generate!` call for the blanket `shepherd:cow/shepherd` world, the host adapter via `bind_host_via_wit_bindgen!`, the `Guest` implementation whose `on-event` dispatches to the named handlers (`init`, `on_block`, `on_chain_logs`, `on_tick`, `on_message`, with undefined handlers ignored), and `export!`. The WIT directory is located by walking up from the consuming crate's manifest, so the emitted paths need no per-module tuning. Re-export it as `nexum_sdk::module` and port the example, balance-tracker and price-alert modules onto it; each strategy MockHost suite passes unchanged. * refactor(http-probe): port onto #[nexum::module] Complete the acceptance list: drop the hand-written wit-bindgen glue, host adapter, and Guest/export block in favour of the attribute, leaving only the init and on-block handlers. Strategy MockHost suite unchanged.
Doc 05 was stamped future-direction and described the superseded macro/TypedState/Signer/HostTransport vision. Replace it with the shipped module-author persona (nexum-sdk + shepherd-sdk + the landed #[nexum::module] macro) and the planned venue-adapter persona (nexum-venue-sdk / per-venue-crate), clearly labelled as design intent tracked by a separate epic. Drop the deferred-features table and cross-link ADR-0009 and sdk.md.
* fix(macros): reject typo'd handlers and malformed impls at expansion A method named on_blocks (or any other on_-prefixed typo) previously compiled as an ordinary helper while its event silently no-opped; the only signal was a dead_code warning that vanishes on pub items. Reserve the on_ prefix for the recognised handler set and error on anything else. Also reject trait impls, generic impls, and impls with no recognised handlers - all previously slipped past the self-type guard and failed later with incidental, unhygienic errors (or compiled into a do-nothing module). Document the wit-bindgen direct-dependency and prelude- shadowing invariants inherited from the generated glue. * docs(migration): rewrite SDK section 7 around the shipped surface Section 7 still described the superseded Signer/Messaging/RemoteStore/ provider() vision - APIs that never shipped - and a #[shepherd::module] macro that does not exist, while doc 05 endorsed it as the authoritative 0.1 -> 0.2 SDK table. Replace it with the real story: 0.1 had no SDK crate, 0.2 ships nexum-sdk/shepherd-sdk host traits, typed errors, and the single #[nexum_sdk::module] attribute. Point doc 05's cross-link text at what the section now says, and fix doc 05 details flagged by the sweep: the CowApiHost parenthetical (submit_order, cow_api_request), the crate trees (proptests.rs in both SDK crates), the testing-story claim (nexum-runtime's feature-gated test_utils::TestRuntime is an in-tree component-level harness), and the first #[nexum::module] mention now names the real nexum_sdk path. * docs(sdk): document the #[nexum_sdk::module] attribute in sdk.md sdk.md is doc 05's designated day-to-day reference but still presented the pre-macro bind_host_via_wit_bindgen! flow as the authoring pattern. Add an authoring section covering the attribute and its handler set, mention it in the intro, and include nexum-macros in the rustdoc command.
feat(wit): author the nexum:value-flow types package Add the egress-neutral value-flow vocabulary at 0.1.0: the settlement domain (evm-chain or offchain), the asset variant (native-token, erc20, erc721, erc1155, service, offchain), the service and offchain descriptors, and the big-endian asset-amount. The package is dependency-free so it can outlive any interface built on it. Every identifier is vetted against WIT keywords, in-flight component-model proposals, and the nine binding-target reserved-word lists, preferring a two-word kebab id wherever a single word is a keyword anywhere: native-token over native (a Java modifier), offchain over external (a Dart keyword), value-flow over value (value-imports is circling that word). A wasmtime bindgen smoke, compiled under test in the host crate through a throwaway world, names every generated type and field by its plain Rust spelling so a keyword collision surfaces as an escaped identifier and fails the build rather than a downstream binding.
feat(wit): author the nexum:intent package with the pool interface Add the venue-neutral intent ontology at 0.1.0 over the value-flow vocabulary: the intent-header (gives/wants/valid-until/settlement/ authorisation), the auth-scheme variant (eip712, eip1271, presign, offchain-sig, unsigned), the intent-status lifecycle with a structured fail-reason, the opaque receipt alias, and a self-contained venue-error (deliberately not embedding the host fault type, so the package's freeze cadence is not pinned to nexum:host versioning). The pool interface routes submit/status/cancel by a venue string with the body as opaque bytes. submit returns a submit-outcome variant from day one: accepted(receipt), or requires-signing(unsigned-tx) for venues whose settlement is a host-signed on-chain call. The unsigned-tx record only describes the call (chain, to, value, input); the host routes it through the guard's host-signed class and fills gas and fees, so adapters still structurally cannot move value. Identifier hygiene follows the value-flow rule (internal-error rather than internal, a Swift declaration keyword). A wasmtime bindgen smoke, compiled under test in the host crate through a throwaway world that imports the pool interface, names every generated type, case, and field by its plain Rust spelling and pins the three pool function signatures with a dummy host impl.
* feat(runtime): introduce the venue-adapter component kind Add the second component kind alongside the event-module. A venue adapter speaks one venue's protocol over scoped transport only and exports the intent adapter face. - WIT: a nexum:intent/adapter interface (derive-header, submit, status, cancel) and a nexum:adapter/venue-adapter world that imports scoped chain and messaging, exports init plus the adapter face, and withholds the core-only primitives. - Bindings: a second bindgen path binding VenueAdapter beside EventModule, reusing the shared nexum:host interfaces via with. - Manifest: a module-kind discriminator defaulting to event-module, plus an adapter capability registry that recognises only the scoped transport. - Engine config: an [[adapters]] table carrying path, manifest, a per-adapter HTTP allowlist, and messaging-topic scopes. - Supervisor: adapters instantiate into supervised stores against a dedicated scoped linker, reusing the store, fuel, and restart machinery; the kind discriminator gates the load path. No routing yet. - Messaging: per-store content-topic scope enforced ahead of the deferred backend. * docs(runtime): keep the venue-adapter world token on one line The bindgen module rustdoc split the `nexum:adapter/venue-adapter` code span across a line break after the slash, so rustdoc rendered it with a stray space. Rewrap so the token stays intact.
) Implement the strategy-facing pool import as a host binding linked into every module linker, dispatching to a shared router that owns the installed adapters. - Bindings: a pool-host bindgen world importing nexum:intent/pool, reusing the intent and value-flow types from the venue-adapter bindings via `with`, so the outcome and error the router hands back to a module are the very types an adapter's submit produced. - Router: resolve a venue id to its adapter, serialise invocation per adapter behind an async mutex (the wasmtime Store is not Sync), sequence derive-header, a no-op guard interposition seam, then submit. Status and cancel pass through without the header, guard, or quota. - Quota: a per-caller sliding-window submission budget, with adapter decode failures charged to the caller so a module feeding garbage bodies exhausts its own budget rather than the adapter's fuel. - Supervisor: adapters instantiate first and install into the router; modules then boot carrying the shared handle, rebuilt identically on restart. An init-failed adapter is loaded but not routable. - Manifest: register nexum:intent/pool as a declarable module capability. - Config: a [limits.quota] section resolving the per-caller budget.
* feat(sdk): ship nexum-venue-sdk over the venue-adapter world Introduce the venue-author persona crate: the VenueAdapter trait and export macro over an in-crate bindgen of the adapter world, the borsh IntentBody derive whose outer version enum fails unknown tags typedly, the typed intent client core over the byte-level pool seam, and typed wrappers over the scoped chain, messaging, and wasi:http imports. The derive expansion lands in nexum-macros and re-exports from the SDK; borsh enters the workspace dependency table. * fix(venue-sdk): wrap the wit chain-error data into Bytes The SDK RpcError.data is Bytes; the wit-bindgen chain-error carries Vec<u8>, so wrap it (Vec -> Bytes is O(1)) when reprojecting.
* feat(sdk): capability-select the wit-bindgen host adapter Split bind_host_via_wit_bindgen! into a types-only base plus one block per capability, selected through a caps list; the zero-argument form keeps emitting the full chain, local_store, logging set for modules compiled against a blanket world. Narrow read_latest_answer to ChainHost + LoggingHost, the two capabilities it exercises, so modules whose worlds omit local-store can still call it. * feat(sdk): emit a per-module world from declared capabilities Teach #[nexum_sdk::module] to read the crate's module.toml and synthesize an inline WIT world whose imports are exactly the [capabilities].required and optional declarations, replacing the blanket shepherd:cow/shepherd world every module compiled against. The built component's imports now equal its declarations by construction, so the runtime's capability check no longer leans on the toolchain eliding unused imports; an undeclared capability has no bindings at all, and an unknown capability name is a compile-time error. The emitted host adapter is capability-selected to match, and a rebuild anchor on module.toml recompiles the module when the manifest changes. Narrow price-alert's strategy bound to ChainHost + LoggingHost: its world imports only its declared chain and logging capabilities, so the full Host supertrait (which adds local-store) is unimplementable there by design. * test(runtime): pin the example component's imports to its declarations The per-module world acceptance: compile the built example component and assert its capability-bearing imports resolve to exactly the manifest's declared set, with no extension interface leaking in. Skips gracefully when the wasm fixture is not built, like the other e2e tests. * docs: record the retirement of import-elision for macro-built modules Macro-built components import what they declare by construction, so capability enforcement is a backstop for them; the elision dependency ADR-0009 flagged now applies only to hand-rolled modules still compiled against the supertype world.
* feat(runtime): add the intent-status case to the host event variant The host event variant gains intent-status(intent-status-update): a venue id, the receipt, and the nexum:intent status vocabulary, so a subscriber sees where an intent is in its life without knowing how the host learnt it. nexum:host now depends on nexum:intent, so every bindgen over the host package carries the intent and value-flow packages on its resolve path, in dependency order; the runtime generates the intent types once and the adapter and pool bindgens remap onto them with 'with'. The module macro recognizes an on_intent_status handler and the example module ships one. * feat(runtime): poll venue statuses and fan intent-status events to subscribers The pool router puts every accepted receipt under watch and, on a configurable cadence ([limits.status_poll] interval_ms), polls each adapter's status export, reporting only transitions: the first poll reports the current status, repeats are deduplicated, a terminal status prunes the watch, and a receipt the venue disowns is dropped. A dedicated event-loop stream fans each transition to modules declaring [[subscription]] kind = "intent-status", optionally filtered by venue; polling only runs when there is at least one subscriber and one installed adapter.
Extend nexum-macros with the venue-adapter counterpart to #[module]: read the crate's module.toml, synthesize a per-component venue-adapter world exporting init plus nexum:intent/adapter and importing exactly the declared scoped transport, then emit the per-cdylib wit-bindgen call, the Guest export glue wiring the adapter's associated functions, and export!. An adapter built this way imports what its manifest declares and nothing else, retiring the toolchain-elision dependency on the venue side rather than as follow-on work. A capability outside the venue-permitted set (chain, messaging, http) is rejected at expansion, so an adapter structurally cannot reach host key material or persistent state. Ship echo-venue as the reference adapter and pin its built component's capability-bearing imports to its single declared capability.
Venue adapters need a way to prove their wire behaviour against the venue's published contract, in a form a non-Rust author can consume: codec vectors and header goldens ship as JSON files (bytes as lowercase hex) rather than Rust-only test code. CodecVectors publishes IntentBody wire bytes and holds a codec to them: round-trip vectors must decode and re-encode byte-exactly, and the failure vectors pin the typed empty, unknown-version, and malformed error contract. HeaderGoldens pairs published bodies with the header a conforming derive-header projects, spelled in serde mirror types whose case names match the WIT. MockTransport composes chain, messaging, and outbound HTTP mocks behind the SDK seams, including the messaging_topics scoping refusal. A reference venue (schema, derivation, and checked-in fixture files pinned by regeneration tests) documents both formats and proves the borsh primitive encodings for authors porting the codec.
feat(examples): pair echo-venue with an echo-client and prove the round-trip Add echo-client, the strategy half of the echo pair: it submits an opaque body through nexum:intent/pool on every block and logs the intent-status transitions the router fans back. Wire both real components through an integration test that proves the intent core end to end, module -> host router -> echo adapter submit and status back, which is the first-train acceptance path. Settle the echo venue instantly (status now reports settled) so the round-trip reaches a terminal transition, and hold its header derivation to the nexum-venue-test kit so echo-venue doubles as the conformance target alongside its tutorial role. Register echo-client in the workspace, the build recipes, and CI.
* docs(wit): pin canonical amount encoding and reserve native-token(offchain) Mandate minimal-length big-endian for asset-amount and require decoders to compare by value, so the same amount has one canonical wire form across the binding targets. Document native-token(offchain) as representable-but-invalid: an off-chain settlement domain has no gas token, so consumers must reject the pairing rather than interpret it. * fix(macros): correct venue manifest error attribution and accumulate venue packages The shared missing-[capabilities] error named #[nexum_sdk::module] even on the venue path; make it macro-neutral so a venue crate is not told to use the wrong attribute. Mirror synthesize's package-accumulation loop in synthesize_venue so a future venue capability carrying an extra WIT package reaches the resolve path (all venue capabilities are packageless today, so the base set is unchanged). * docs: normalise doc comments to British -ise spelling Align the authorise/authorisation prose in the intent WIT and the conformance kit with the British spelling used elsewhere and with the authorisation identifier itself. Doc comments only; no identifier, wire format, or API name changes. * docs(runtime): document the pool submit charge asymmetry State the deliberate rule that a forwarded submission is charged once the guard admits it regardless of the venue outcome, while a derive-stage non-decode venue error is left uncharged so the caller may retry.
* feat(cow-venue): scaffold default body slice with borsh IntentBody codec Stage the CoW venue as a crate of feature slices. The default `body` slice carries the venue-neutral order and composable intent body types and the borsh IntentBody codec over the outer per-venue version enum, so a future adapter component or a strategy module can link the bodies and codec without the host-side CoW machinery. The slice links only the venue SDK (for the IntentBody derive) and borsh; `--no-default-features` drops it to an empty crate. shepherd-sdk gains a re-export shim under `cow` so the module ports can move off the legacy path without a breaking rename. * style(cow-venue): use -ize spelling and drop test reach into borsh __private
* feat(cow-venue): drive cow retry classification from a shipped data table Ship the CoW orderbook errorType retry policy as data in crates/cow-venue/data/classification.toml, readable and editable by non-Rust authors, and add a `client` feature slice that embeds it, exposes a typed CowClient bound to the CoW venue, and a table-driven classification API returning the keeper RetryAction. Replace the hand-coded classify_api_error/is_retriable in the cow error surface with the table lookup, preserving the already-submitted and drop-on-unrecognized behaviour. A throttle errorType now maps to Backoff, so every RetryAction arm is reachable from the table alone, guarded by a data-vs-code parity test and an untyped-parse test that proves any TOML reader consumes the same file. * perf(cow-venue): generate the classification table at build time shepherd-sdk unconditionally enables the cow-venue client slice, and the wasm modules (twap-monitor, ethflow-watcher) reach classify_api_error through shepherd_sdk::cow::run, so the runtime LazyLock TOML parse linked the full toml/serde/winnow stack into every guest: the twap-monitor wasm grew ~237 KiB (350 KiB -> 586 KiB). Move the parse to build.rs. It reads data/classification.toml, validates the table invariants, and emits a static lookup table; the runtime slice carries only generated rows and no TOML parser. serde/toml/thiserror become build- and dev-only. The parse and invariants live in a shared classification_data module that build.rs and the parity test both link, so the data-vs-code parity test now re-parses the shipped file and checks the generated table against it. The guest returns to 351 KiB.
* fix(cow): use canonical InvalidEip1271Signature errorType spelling The permanent_kinds_yield_drop test asserted on "InvalidErc1271Signature", which is not a real CoW orderbook errorType (the OpenAPI OrderPostError enum and cowprotocol's OrderbookApiErrorType both spell it InvalidEip1271Signature, matching data/classification.toml). The phantom spelling passed only via the unlisted-default Drop, so it never exercised the real table row. Use the canonical spelling so the test covers the actual entry. * docs(cow-venue): ratify deliberate divergence from cowprotocol retry_hint shepherd-sdk already depends on cowprotocol, whose ApiError::retry_hint() classifies the same orderbook errorTypes and disagrees with this table on several (e.g. it retries/backs off InvalidEip1271Signature, InsufficientBalance, InsufficientAllowance and InvalidAppData where this table drops, and backs off TooManyLimitOrders for an hour rather than 30s). The table is deliberately not delegated to it: it is shepherd's own, more conservative policy, kept as data of record so a non-Rust author owns it and the guest client slice stays free of the upstream error module. Record that this divergence is intentional so a future maintainer revisits the policy here rather than swapping the source of truth. (Also fixes the -ise spelling in the same comment.) * style(cow-venue): adopt British -ise spelling to match house convention The crate used American -ize (normalizes/normalization, unrecognized) while the adjacent domain code (shepherd-sdk cow/order.rs normalised, composable.rs unrecognised, run.rs) and the repo at large use British -ise. Align the prose so the sibling modules read consistently. * fix(cow-venue): allow GenAction variants unused by shipped data GenAction's variants are constructed only by the build-generated table, so a classification.toml that carries no row of a given action leaves that variant unconstructed and trips the -D warnings build. Since which actions appear is a property of the data, not the code, mark the enum allow(dead_code) so a data-only edit cannot break the workspace lint gate.
…ve 1, ADR-0013) (#334)
Tip-level residual after the chassis to keeper rename was folded down through the train. Carries the doc-comment prose the fold could not move mechanically (the private-keeper caveat, keeper-run wording) and the current-state design docs brought in line with the Verdict poll seam (PollOutcome to Verdict, decode_revert to LegacyRevertAdapter).
…351) * docs: multi-chain deployment patterns — Mainnet/Gnosis/Arbitrum/Base (#124) New docs/deployment/multi-chain.md covering the verbatim M5 grant deliverable: per-chain [chains.<id>] + env-var wiring, CoW orderbook URL slugs per network, per-chain contract addresses (CREATE2-stable vs EthFlow per-network caveat), the [[subscription]] duplication pattern with twap-monitor and ethflow-watcher examples, event topic reference, and resource sizing guidance. * docs(multi-chain): correct the EthFlow address claim and scope statements Review catches: - The EthFlow section claimed the address is per-network and told operators to hunt for a different mainnet address. The current production deployment is address-identical on every supported chain (cowprotocol's ETH_FLOW_PRODUCTION, from networks.prod.json) - the old text would have steered a mainnet port toward the legacy v1.0.0 deployment and missed every OrderPlacement event. The real caveat is narrower and now stated as such: sameness is not a CREATE2 guarantee, legacy per-version deployments exist, verify on version bumps. - "every chain the orderbook supports" was false (the Chain enum also carries BNB, Polygon, Avalanche, Linea, Plasma); the table is scoped to the chains this repo's modules target. - Dropped the unsupported "Gnosis volume roughly equal to Mainnet" claim; require_ws violations are an ERROR log, not a warning; the RPC recommendation names a rate requirement instead of a specific provider's free tier. --------- Co-authored-by: Luiz Gustavo Abou Hatem de Liz <luizgustavoahsc@gmail.com>
…ata (#352) * docs: stale-reference sweep — engine rename, Dockerfile, ADR-0007 errata (#123, #108, #62) - deployment.md: replace the "planned for M5" Dockerfile sketch with a pointer to the real Dockerfile + docker.md; update nexum-runtime log directive examples - qa-signoff.md: nexum-engine → nexum-runtime in crate-dependency row - adr/0007: add errata noting the nexum-engine → nexum-runtime rename and the [patch.crates-io] retirement now that cowprotocol 0.1.0 is on crates.io * docs: fix the sweep's own stale claims - cowprotocol override, production.md 3.1 Review catches on the stale-reference sweep itself: - ADR-0007 errata and sdk.md Versioning both claimed cowprotocol is a clean 0.1.0 registry dependency "with no git override". The branch's own Cargo.toml pins 0.2.0 WITH an active [patch.crates-io] override to nullislabs/cow-rs (pending the hash-only OrderCreationAppData constructor release). Both now describe that state and point at the patch-block comment as the source of truth. - production.md 3.1 still shipped the "interim" hand-rolled Dockerfile recipe issue #123 flagged; replaced with a pointer to the real root Dockerfile and docs/deployment/docker.md. - 02-modules-events-packaging.md: un-garble the parenthetical the earlier rename made self-contradictory ("was module.toml in earlier drafts" -> "was nexum.toml"). --------- Co-authored-by: Luiz Gustavo Abou Hatem de Liz <luizgustavoahsc@gmail.com>
* test(event-loop): add ordering and graceful-shutdown tests (#56, #58) Four new tests covering the two open issues: Issue #56 — event ordering guarantees: - `open_block_streams_opens_one_task_per_chain`: structural check that N chains → N independent reconnect tasks, documenting the per-chain task isolation that prevents stream-A starvation from blocking stream-B events. - `open_chain_log_streams_opens_one_task_per_subscription`: same structural check for chain-log subscriptions. - `run_delivers_block_and_chain_log_events_without_starvation`: integration test over a zero-module supervisor that pre-queues one block and one chain-log event, runs the loop, and verifies it drains without hanging — the `biased` select delivers both event kinds in the same session. - `harness_delivers_block_and_chain_log_events_without_starvation`: E2E test over the real example module (skipped when wasm not built) that pushes a block and a chain-log and waits for both dispatch log lines. Issue #58 — graceful shutdown completion: - `run_drains_reconnect_tasks_cleanly_on_shutdown`: verifies `run()` awaits `tasks.shutdown()` before returning — reconnect tasks observe a closed channel (ReceiverGone) rather than an abort, proving clean teardown. - `harness_shutdown_after_push_completes_cleanly`: E2E test that calls shutdown immediately after a block push; `wait()` returning Ok proves no partial dispatch corrupts module state. * test(event-loop): make the #56/#58 tests falsifiable Review follow-ups - three of the six tests could not fail for the property they claimed to verify: - run() now returns its (blocks, chain_logs) dispatch tally (the same numbers the shutdown log line reports). The no-starvation test asserts both queued events were drained instead of merely observing that run() terminates on the shutdown timer - a broken or reordered select arm now leaves a count at 0 and fails. - New direct test: a reconnect task parked on a dropped receiver exits with TaskExit::ReceiverGone on its own, joined on the bare handle. The previous test claimed to verify this through TaskSet::shutdown, which aborts every handle before joining, so ReceiverGone was never exercised; its doc comment now states the abort-then-join contract it actually proves. - harness_shutdown_after_push_completes_cleanly raced shutdown against dispatch and accepted either outcome. Replaced with harness_shutdown_preserves_completed_dispatch: prove the dispatch completed, tear down, then re-read the log record after teardown. - The harness no-starvation test now queues both event kinds before awaiting either, so the biased select genuinely arbitrates two ready streams instead of replaying the two pre-existing sequential tests. - New harness_delivers_blocks_in_push_order: blocks 7,8,9 pushed back-to-back must surface in the module's log records in that order - issue #56's ordering guarantee, previously untested. - run()-level tests wrapped in tokio::time::timeout so a shutdown-path hang fails in 10 s instead of stalling the suite to the CI job limit. * test(event-loop): adapt test call sites to the ChainLogSub struct The train base moved under the branch again: open_chain_log_streams now takes Vec<ChainLogSub> (resume cursors + max_lookback) instead of (module, chain, filter) tuples, and the rebase left the two test call sites building tuples. Construct ChainLogSub with no cursor and no lookback - these tests exercise stream/task plumbing, not resume. --------- Co-authored-by: Luiz Gustavo Abou Hatem de Liz <luizgustavoahsc@gmail.com>
#354) * feat(chain): cap JSON-RPC response size before lowering into the guest (#154) Adds a configurable `[limits.chain] response_max_bytes` knob (default 1 MiB) that is enforced host-side in `chain::request()` before the response string is copied into the guest heap. Oversized responses are rejected with an `InvalidInput` fault, a `WARN` log, and a dedicated `shepherd_chain_response_capped_total` metric. * fix(#154): bound batch aggregates, harden config, test the real wiring Review follow-ups on the chain response cap: - request_batch: the per-entry cap bounds each body, but the aggregate Vec<RpcResult> lowered into guest memory was unbounded - ~64 entries just under the cap is ~64 MiB, exactly the guest-heap saturation issue #154 exists to prevent, reachable via the block-range chunking the issue itself recommends. A running total now converts entries past the cap into typed invalid-input results. - config: renamed [limits.chain].response_max_bytes to response_body_max_bytes (u64) for exact symmetry with the [limits.http] sibling; a degenerate 0 saturates to 1, matching the logs/poison zero handling. - new harness test drives the cap through the real path - config -> ModuleLimits -> HostState -> chain::request - with the price-alert module: an over-cap oracle response surfaces to the guest as the typed fault and never reaches classify. The existing unit tests only covered the free function, so a deleted and_then would have passed the suite. - TOML parse tests for [limits.chain] (absent -> 1 MiB default, override, zero saturation), mirroring the HTTP section's tests. - engine.example.toml documents the new section; warn message dash matches house style. --------- Co-authored-by: Luiz Gustavo Abou Hatem de Liz <luizgustavoahsc@gmail.com>
# Conflicts: # crates/nexum-runtime/src/builder.rs # crates/nexum-runtime/src/supervisor.rs # crates/shepherd-backtest/src/replay.rs
Fold the M0 runtime completion (#424) into the intent/venue train: nexum-tasks executor, WASI capability gate, dispatch rate limit, per-module state quota and graceful drain. Reconciles the shared event-loop, supervisor, engine-config and manifest surfaces where both lines grew, and repoints the slow-host fixture at the multi-package wit path.
#600) * docs: trim platform generalisation and linker seam to current contract Rewrite docs/08 as a terse description of the shipped layered WIT model: keep the Layer 1/2/3 world model, the verified universal interface signatures, the venue-adapter Layer 3 mechanics, and the complete WIT package layout; collapse the mobile, WebView, and super-app targets and their per-primitive implementation tables to one sentence each; cut the motivation essay, the host adapter specification, and the summary. Reduce the 0.2 status banner to one line and point the SDK and taxonomy topics at their owning docs. Rewrite docs/design/linker-extension-seam.md against the current Extension trait: drop the retired cow-api / shepherd-cow-host / CowBackend example, describe namespace, capabilities, link, service, provider, admit and event members, the HostServices and ExtState reach paths, and the videre venue platform as the live extension the shepherd binary registers. Align the reproduced WIT with wit/: chain request-batch takes rpc-request and returns rpc-result, the event variant carries custom, and identity and chain are stated at their actual reference-host behaviour. Part of #598. * docs: unwrap paragraphs to one logical line for diff-friendliness Hard-wrapped prose churns diffs: a one-word edit reflows the whole paragraph. Join each paragraph onto a single logical line and let it soft-wrap. Content unchanged (word and heading counts preserved); code fences, tables, lists, blockquotes and headings untouched.
* docs: rewrite sdk and rpc design docs to the shipped contract Rewrite 07 (rpc namespace) from a pre-code design essay into a terse description of the shipped chain interface: the chain.request / request-batch WIT surface, the closed ChainMethod read-only surface enforced host-side, the separate identity signing interface, the HostTransport / ProviderHost / block_on alloy provider seam, synchronous module handlers, videre:venue as the order-submission path, and the MockHost testing seam. Delete the aspirational cow-api namespace, shepherd-sdk crate, #[shepherd::module] macro, the Cow SDK type, MockProvider and MockCow, and the async-handler / block_on-elimination / provider-injection narrative that never shipped. Fix 05's videre-sdk crate layout: poll_once lives in client.rs, not a non-existent rt.rs, and add the event.rs and value_flow.rs entries. Part of #598. * docs: unwrap paragraphs to one logical line for diff-friendliness Hard-wrapped prose churns diffs: a one-word edit reflows the whole paragraph. Join each paragraph onto a single logical line and let it soft-wrap. Content unchanged (word and heading counts preserved); code fences, tables, lists, blockquotes and headings untouched.
* docs: trim user-facing docs, retarget the first-module tutorial Retarget tutorial-first-module.md from the removed stop-loss/cow-api walkthrough to the shipped price-alert example (block subscription, chain read, ABI decode) with current crate names and the #[nexum_sdk::module] macro surface; drop the aspirational time-budget framing. Delete qa-signoff.md, a dated pre-review sign-off snapshot. testing-runtime-harness.md: drop the nonexistent shepherd-sdk-test mock surface (module logic tests use nexum-sdk-test plus videre-test's transport mocks), repoint the sdk.md anchor, trim the intro. scripts/README.md: purge em dashes, drop the deleted e2e-prep.md reference and the stale stop-loss note. Part of #598. * docs: unwrap paragraphs to one logical line for diff-friendliness Hard-wrapped prose churns diffs: a one-word edit reflows the whole paragraph. Join each paragraph onto a single logical line and let it soft-wrap. Content unchanged (word and heading counts preserved); code fences, tables, lists, blockquotes and headings untouched.
Trim discursive and stale rustdoc across the workspace: cut the wit-bindgen companionship and "why no generate!" essays in nexum-sdk to the factual contract, drop the "before this macro existed" narrative in wit_bindgen_macro, and remove the #94 planning reference in nexum-sdk-test. Reduce version-promise notes to current behaviour: the identity, messaging, and remote-store host impls are stated as unimplemented stubs (confirmed against the code) rather than "deferred to 0.3"; manifest and engine-config headers drop the 0.3 and M3/M4 scheduling; restart_policy drops the 0.3/M5 follow-up and tuneable promises. Remove the external-project rationale citation in provider_pool ("per alloy's own guidance"), retarget load-gen off the M4 label and the deleted e2e-prep.md reference, and purge em dashes from the touched doc comments. Part of #598.
* docs: operations sweep, delete dated reports and trim runbooks Delete every point-in-time artefact (load, backtest, baseline, and e2e report snapshots, the m3 edge-case validation write-up, and the e2e run-prep punch list) so no dated result crosses the carve. Trim the five runbooks (m2, m3, e2e, load, soak) to repeatable numbered steps: cut motivation, milestone scheduling, grant-evidence framing, and result commentary, and purge em dashes. Salvage the reusable UID and ComposableCoW calldata re-derivation recipes from the deleted e2e-prep into the e2e runbook. Align the engine run commands to the shepherd binary the just recipes and load-run.sh invoke, and trim the e2e report template to its fields. Part of #598. * docs: unwrap paragraphs to one logical line for diff-friendliness Hard-wrapped prose churns diffs: a one-word edit reflows the whole paragraph. Join each paragraph onto a single logical line and let it soft-wrap. Content unchanged (word and heading counts preserved); code fences, tables, lists, blockquotes and headings untouched.
* docs: prune and de-drift deployment and production docs Trim the deployment and production corpus to the current contract and fix ownership: docs/deployment.md owns the engine.toml reference, module artefact builds, and local runs; docs/production.md owns the production deploy (systemd, backup, observability wiring); docs/deployment/docker.md owns containers; docs/deployment/multi-chain.md owns multi-chain config; docs/06-production-hardening.md owns the hardening design facts and stops restating deploy steps. Each of the others links the owner rather than repeating. Verify every config key, default, metric name, port, and binary against the code on dev/m1: the production binary is shepherd (not nexum-cli), resource caps live under [limits] (not [engine.limits]), the redb file is local-store.redb, the metric prefix is shepherd_* with the eleven-metric surface the runtime actually emits, and the orderbook URL override is the cow-venue adapter's own [config] orderbook-url rather than an engine-side extensions.cow table. Drop the fabricated cli subcommands, epoch mechanism, health endpoint, and nexum_* metric table. Purge em dashes and repo-name and planning drift. * docs: unwrap paragraphs to one logical line for diff-friendliness Hard-wrapped prose churns diffs: a one-word edit reflows the whole paragraph. Join each paragraph onto a single logical line and let it soft-wrap. Content unchanged (word and heading counts preserved); code fences, tables, lists, blockquotes and headings untouched.
Rewrite docs 00-04 and the diagram captions as terse descriptions of the current contract, cutting rationale essays, grant-milestone and platform-target planning material, and 0.3 version promises reduced to current fact. Verify every retained claim against crates/nexum-runtime: engine.toml [limits] keys and defaults (fuel 1B, memory 64 MiB, state 50 MiB, deadline 120s), the six-primitive event-module world, and the single-file keccak256-prefixed local store (ADR-0003). Correct substantive drift: module discovery is filesystem-only in 0.2 with ENS and registry as design direction; the shepherd:cow package now carries only cow-events and order submission is the videre:venue venue-adapter contract; the local store commits per host call rather than per event. Part of #598.
docs: unwrap remaining hard-wrapped markdown for diff-friendliness The last tracked markdown outside the docs cruft-sweep areas. Join each paragraph onto one logical line so a one-word edit no longer reflows the block. Content unchanged (word and heading counts preserved).
…atomic (#610) Record the decision from the local-store durability study: per-call committed durability is the contract, per-event atomicity is rejected (it would roll back the keeper Journal's RESERVED marker on the trap it exists to survive), and one opt-in apply batch verb is the sanctioned atomicity scope. Closes the doc-vs-code gap that claimed a per-event rollback that never existed.
Compress crate root, host trait seam, and keeper store docs to house-style terse one-liners: cut the module-tour essay, the 70-line Host doctest stub, external-project comparisons, and per-field restatement, while preserving the fail-open, idempotence, ordering, and corrupt-tag reconcile contracts. Part of #613
Apply the terse-rustdoc house style to videre-test, videre-status-body, and nexum-sdk-test: one-line item docs, a few-line crate and module headers, wire-format and error contracts preserved but compressed, tutorial and rationale narrative cut. Part of #613.
…rn write prefix (#612) nexum-sdk-test grows TrapStore, a LocalStoreHost wrapper that counts set and delete calls and, once armed, traps after the nth write and faults every later operation until disarmed, so nothing past the trap executes. The keeper tests sweep the accepted-submit tick through every torn write prefix (trap after 0, 1, and 2 of its 3 writes: reserve set, commit set, refusal-marker delete) and hold the next sweep to convergence: the journal ends COMMITTED, the venue holds exactly one order via the re-POST idempotency backstop, no reservation stays stranded, and a further tick is a pure idempotent skip. The review rule the sweep enforces sits next to the harness: no in-store invariant may span two set calls unless the intermediate state is self-healing or the writes ride the atomic apply batch verb (#609).
Apply the terse-rustdoc house style across the shipped modules (twap-monitor, ethflow-watcher), example modules, test fixtures, and the load-gen and orderbook-mock tools: one-line item docs, module and crate headers cut to a few factual lines, restated signatures, rationale, history and external comparisons removed, invariants and fault-injection contracts preserved and compressed.
Compress engine_config.rs, supervisor.rs, and supervisor/tests.rs doc comments to the terse house style: one-line item docs, config fields cut to their contract (units, defaults, saturation), restart and poison invariants kept but compressed, and the config-schema, history, and rationale essays dropped. Part of #613.
De-stale the word strategy to keeper (venue-submitting modules) or module (generic guests) across the docs tree, and repoint the renamed module logic files: ethflow-watcher uses keeper.rs and the read-only example modules use logic.rs. No banner comments were present in docs. No behaviour or code change.
De-stale the word "strategy" in crate comments and doc comments to "keeper" (a keeper submits to venues) or "module" (the generic guest) as fits each context, leaving the proptest crate's own `Strategy` trait in nexum-sdk/src/proptests.rs untouched. Kill banner/section-divider line comments throughout crates/**, including test modules, per the umbrella hygiene sweep. Part of #625.
Rename the modules' pure-logic files off the stale strategy name: ethflow-watcher submits observations to venues so its logic is keeper.rs, while the read-only balance-tracker, http-probe and price-alert examples become logic.rs. Update every mod declaration, path reference, module.toml and Cargo.toml comment to match. De-stale the word strategy in module comments and doc comments to keeper (ethflow-watcher, twap-monitor) or module (generic), and kill the banner divider comments throughout modules.
Add Journal::guard to the nexum-sdk keeper: it reserves the submission key with the encoded body, runs the async submit closure, then disposes of the reservation as the closure's Disposition directs (Commit on accept, Release on a known non-accept, Park on a rate-limit hint, Retain for reconcile). The reserve-effect-commit order is fixed by the API shape, so a caller cannot run the effect unreserved or leave the marker unsettled; an existing marker short-circuits without running the closure. The combinator factors out the pattern the videre-sdk keeper run and the composable-cow run already inline, without changing either; markers stay on plain set. Tests cover the combinator semantics over the mock store and drive it through the reserve/commit/reconcile path with the FlakyCommit and counting-venue fixtures, asserting exactly-once and no stranded reservation.
Expose the atomic batch verb on the seam: LocalStoreHost gains WriteOp and apply, whose default is an explicit per-op set/delete fallback for arbitrary impls such as mocks, and the WitBindgenHost adapter overrides it with the host's apply verb so a flushed batch is all-or-nothing on the real host. Add nexum_sdk::store with the #609 Wave 2 helpers: a WriteBatch builder flushed in one apply call, clear_prefix, borsh-typed TypedCell and TypedMap, and a u64 Counter, so module code hand-rolls neither serialization nor batching.
Rewire submit_ready onto Journal::guard: the six inline journal.mark/reserve/commit/release calls that carry the #572 reserve-before-effect ordering fold into one guard call, with the venue outcome mapped to a Disposition (Accepted commits, requires-signing and known refusals release, an unknown outcome retains for reconcile). Behaviour is identical: all 23 composable-cow run tests including w1/w2/w3 and anti_572 pass unchanged. The remaining code is the CoW-specific outcome handling (refusal clear, retry ledger, tracing) that no primitive absorbs; the value is that the durable-effect ordering is no longer inlined, so a second venue's keeper reuses it rather than re-deriving it. Part of #609. Stacks on the Journal::guard combinator (#629).
refactor(packaging): reorganize into three-grouping path-dep workspace Move all workspace members into nexum/ (L1), videre/ (L2) and shepherd/ (L3) repo-root dirs as a single cargo workspace with intra-grouping path-deps, so the tree keeps building as one unit with a shared Cargo.lock right up to the physical carve (#407). This is the refactor-now-cut-later step of the gated three-repo split (M5). Add the carve-groups dep-sync gate (scripts/check-carve-groups.sh, CI job, justfile recipe) enforcing the acyclic nexum <- videre <- shepherd invariant from the physical layout, so no upward edge can become a circular repo dependency at the carve. Document the transitional cross-repo dependency medium (path-deps -> git-tag pins -> crates.io) in docs/design/carve-workspace.md. WIT stays in a single root wit/, resolved via the manifest-dir ancestor walk; the per-group wit/ + wit/deps split is deferred to the wit-deps flip (#404/#405). The hardcoded wit_bindgen::generate! path-lists were re-based one level deeper by the move. Align the justfile with the already-optimized CI: test/test-e2e/ci use cargo nextest run, ci runs doctests separately and appends -D warnings to RUSTFLAGS/RUSTDOCFLAGS instead of clobbering the devshell mold flags. sccache is already wired via the flake devshell and the CI workflow env. Fix the test workspace-root locators the move broke: replace every hardcoded CARGO_MANIFEST_DIR.parent().parent() (valid only at the old crates/<x> depth) with a workspace_root() that walks to the topmost ancestor carrying a Cargo.toml, and group-prefix the module manifests those e2e tests load. These had silently skipped locally (no CI env) into a hollow green; CI's fail-loud path caught them. Re-validated with CI=1. Closes #403.
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.
Rebase-merge the M1 train (incl. #636) from dev/m1 into develop. Redo of the accidentally squash-merged #637; identical tree (2436c50) already passed CI on #637.