chore(deps)!: migrate to nexum-runtime fd16db7 and videre 1da06af, cow-venue becomes native Rust - #675
Merged
Conversation
nexum-runtime to fd16db7 and videre to 1da06af, with wit/deps/ re-vendored from both. The vendored nexum:host loses messaging, identity, remote-store and query-module, and gains trigger-module. This commit alone does not compile: it is the base the migration units build on. AI Assistance: Claude Code used for the pin bump and re-vendor.
Rename `module.toml` to `component.toml` and rewrite it against the new manifest model: `[component]`, a table-valued `[dependencies]` map, and `[[trigger]] on =` rows. Drop the retired `kind`, `optional`, and the placeholder `component` digest, which the runtime now verifies against the loaded bytes. Fold `on_chain_logs` into `on_event`, which takes one log and reads the chain id off the WIT record before the alloy conversion drops it. Move `nexum_sdk::events` to `nexum_sdk::sol_events` and `ChainLogParts` to `LogParts`. AI Assistance: Claude Code used for the API survey and the migration edits
Follow nexum-runtime fd16db7, which renamed the keeper watch vocabulary to commitment: `WatchSet` to `CommitmentSet`, `WatchRef` to `CommitmentRef`, and the `watch` parameter on `Gates::*`, `Poller::poll` and `Retrier::apply` to `commitment`. Rename the run composition's locals, log lines and doc wording to match, and carry the same pass through the run acceptance tests. Add the new `LocalStoreHost::list_entries` verb to the `FlakyCommit` test double. It delegates to the inner store rather than taking the trait default, so the mock keeps its paging behaviour; only the write path still injects the commit fault. BREAKING CHANGE: the keeper store prefix moves from `watch:` to `commitment:`. An existing store's `watch:` rows are never read and never deleted after the upgrade, and their paired `next_block:`, `next_epoch:` and `refused:` keys are orphaned. The `submitted:` and `observed:` journal prefixes are unchanged. No migration ships here. AI Assistance: Claude Code used for the rename pass and the test updates.
Port the keeper to nexum-runtime fd16db7 and videre 1da06af. Rename `module.toml` to `component.toml` and rewrite it to the new schema: `[component]` replaces `[module]`, `[dependencies]` replaces `[capabilities] required = [...]`, and each `[[trigger]] on =` replaces a `[[subscription]] kind =`. The two `chain-log` subscriptions become `on = "event"`, the block subscription becomes `on = "block"`, and the intent-status subscription keeps its kind as videre's own extension trigger with a `venue = "cow"` filter. Drop the zero placeholder content pin: the key is now `[component].digest` and the runtime verifies it against the loaded bytes, so a placeholder refuses the load. `[venue] body_version` is unchanged. Rename `on_chain_logs` to `on_event`, which now takes one log instead of a batch, and adopt the keeper vocabulary rename: `WatchSet` to `CommitmentSet`, `WatchRef` to `CommitmentRef`, `watch_key` to `commitment_key`, and the stored prefix `watch:` to `commitment:`. `nexum_sdk::events::ChainLogParts` becomes `nexum_sdk::sol_events::LogParts`. The drop-line assertion now matches on the commitment key and the verb rather than on `composable_cow::run`'s exact wording, so the two crates can be reworded independently. This is a port: the submit path still relays EIP-1271 signatures only. AI Assistance: Claude Code used for the API migration and test updates.
The runtime deleted the extension-installed component path, so a venue is no longer a guest wasm artifact an extension installs. `videre_host::platform()` takes no config, and the composition root registers each venue on the `VenueRegistry` before launch. `shepherd-engine` gains a lib target so the composition root is testable: `ShepherdRuntime` and the new `venues` module move there, and `main.rs` is the thin binary over it. `venues::register` reads the venue set from the opaque `[extensions.videre]` table and builds the cow venue through `cow_venue::register`. Two operator capabilities are lost with `[[adapters]]`, not relocated. Per-venue outbound-HTTP confinement is gone: `http_allow` fed the wasi:http gate, which reaches guest components only, and a native venue owns its own client. Operator-swappable venues are gone: `path` and `manifest` have no successor, so changing the venue set means a new binary. Both are recorded in the `venues` module docs and in engine.example.toml. The eight operator configs lose `[[adapters]]` and gain `[extensions.videre.venues.cow]`, carrying what the adapter manifest's `[config]` table held. Each `[[modules]]` entry gains the mandatory operator-written `id`; manifest paths become `component.toml`; the `[limits]` scalars move to `[policy]` as `max_fuel_per_dispatch` and `max_memory_bytes`; and every config but the reference template relaxes `require_component_digest`, because each rebuilds its components per run. tests/operator_configs.rs holds all eight files to the runtime's own parser and to the venue section, so a retired key refuses in CI rather than at an operator's boot. platform_seam.rs drops the venue world-contract test, which had no subject left, and boots its keepers through `BootScenario`. AI Assistance: Claude Code used for the migration and the new tests.
The runtime deleted the extension-installed component path, so a venue can no longer be a guest wasm component. `CowAdapter` now implements `videre_host::VenueInvoker` in-process and reaches the orderbook through its own reqwest client instead of a scoped wasi:http import. The orderbook protocol logic is unchanged: UID reconciliation, already-held folding, the classification-table projection and the status lifecycle mapping are the same code, made async over a new `Transport` seam. `cancel` still returns `Unsupported`, because the venue holds no keys. The `#[videre_sdk::venue]` macros both go: the adapter export face had no host to load it, and the client-side marker held the venue id to a manifest `[component] name` that no longer exists. The id is now the `VENUE_ID` literal, and `register` uses it so the registered id cannot drift from the one the keeper client routes to. The three `module*.toml` manifests are deleted. Their venue id, chain, orderbook url, owner and timeout are `CowConfig`; their `body_versions` is `BODY_VERSIONS`. Their `http_allow` grant has no in-process equivalent, so the transport refuses redirects and the reachable host is whatever `CowConfig::orderbook_url` names. The `adapter` feature is renamed `venue`, the crate drops its cdylib and `wit-bindgen`, and the wasm build of cow-venue is removed from the justfile, CI and the Dockerfile. AI Assistance: Claude Code used for the port, the transport seam and the tests
`shepherd-engine` called `cow_venue::register` with three arguments; it takes two, because the venue id comes from cow-venue's own `venue_id()` so the registered id cannot drift from the id `CowVenue::ID` routes to. The Dockerfile still copied `module.toml`, and the e2e scripts still grepped the `watch:` log marker, which is `commitment:` now. Neither file was in any unit's scope. AI Assistance: Claude Code used for the merge reconciliation.
The release branch is being retired. Its tip 1da06af is not an ancestor of main, because the catch-up PR squash-merged, so deleting the branch would orphan the commit this pinned. 66c8c3b carries the identical tree. AI Assistance: Claude Code used for the re-pin.
An event trigger without `resume` re-opens at head, so every log mined
during downtime is lost. Both keepers build state only from logs, so
that loss is silent and permanent.
twap-monitor holds a commitment only because it saw the matching
`ConditionalOrderCreated`. A restart at head never learns of an order
registered while it was down, and that order is never polled or
submitted. The `ConditionalOrderRemoved` stream resumes with it:
backfilling creates alone would replay an order whose removal fell in
the same gap.
ethflow-watcher has the same shape. A placement seen once is the only
thing that puts an order under the host watch.
`max_lookback` stays unset on all three. A cap drops the oldest missed
blocks, which is the loss being fixed here.
Replay is already idempotent: `persist_commitment` overwrites in place
keeping the newest stamp, and ethflow-watcher short-circuits on
`observed:{uid}`. Both have tests. A first boot has no cursor and still
starts at head, so this backfills a restart gap only.
The two twap-monitor cursors are independent, so a backfilled removal
can land before its own create and briefly resurrect a commitment. The
next poll reverts `SingleOrderNotAuthed`, which `LegacyRevertAdapter::
classify` folds to `Invalid`, and the commitment drops.
AI Assistance: Claude Code used for the manifest change and for tracing
the resume-cursor and replay-idempotency paths.
`resume` carries a cursor across a restart, but a first boot has no cursor and opens at head. Both keepers build their whole state from logs, so a fresh daemon never learns of anything that happened before it ran: a ComposableCoW conditional order can stay live for weeks, and a TWAP registered last month is invisible and never polled. `start_block` seeds that first boot from the contract's deployment block, resolved from each deploy transaction: twap-monitor ComposableCoW Sepolia 5072748 ethflow-watcher CoWSwapEthFlow Sepolia 7541028 ComposableCoW is one CREATE2 address on every chain but a different block per chain, so mainnet is 17883049. EthFlow has had several per-network and per-version deployments, so its block belongs to the pinned address and must be re-derived when that address changes. The seed applies only while no cursor is stored; after the first committed chunk the store wins, so this is a one-time floor rather than a rescan point. Both twap-monitor streams seed together, because backfilling creates while removals start at head would replay every order ever registered and retire none of them. The pins move in lock-step: nexum-runtime fd16db7 -> 2ed882f for the `start_block` key itself, and videre 66c8c3b -> fd8af02 for its matching bump. A split pin resolves two copies of nexum-runtime whose Extension seam types do not match. Adds `module_manifests.rs`, which pins `resume` and `start_block` on every shipped event trigger. Dropping either key still parses, still boots and still passes every other test, so nothing else in the repo would notice the keeper going quietly blind. AI Assistance: Claude Code used for the pin bumps, the deployment block resolution and the manifest pins.
This was referenced Sep 1, 2026
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.
Migrates shepherd to nexum-runtime
fd16db7and videre1da06af, so ccow-monitor can ship.Why this is a rewrite and not a pin bump
Upstream deleted the extension-installed component path.
ProviderKind,HostService,ProviderInstance,build_provider_linker,ActorSlot,SupervisedStoreand the runtime'sLivenessare gone, so an extension can no longer install or supervise a guest component. A venue is now a native Rust adapter that the composition root registers in code.What changed
crates/cow-venueis a nativevidere_host::VenueInvokerrather than a wasm guest. It dropscrate-type = ["lib", "cdylib"],wit-bindgen, the#[videre_sdk::venue]attributes and its threemodule*.tomlmanifests. Its feature is renamedadaptertovenue. The published codec vectors and header goldens are unchanged, which is the evidence the wire behaviour did not move.crates/composable-cowand both keeper modules move from the Watch vocabulary to Commitment. Manifests becomecomponent.tomlwith[component],[dependencies]and[[trigger]].crates/shepherd-enginegains a lib target so the composition root is testable, and registers the CoW venue in Rust. The eight operator configs lose their[[adapters]]tables and gain an[extensions.videre.venues.cow]section. A newtests/operator_configs.rsparses all eight through the realEngineConfigparser, so schema drift fails in CI rather than at an operator's boot.Capability losses, recorded rather than papered over
Per-venue HTTP egress confinement is gone.
http_allow = ["api.cow.fi"]fed the wasi:http gate, which only reaches guest components. A native venue owns its own client, so nothing bounds its egress. Mitigated in cow-venue by refusing redirects, so the reachable host is exactly whatorderbook_urlnames and a 307 cannot re-send a signed order elsewhere.[policy].http_denystill scopes guests but does not reach a venue.Operator-swappable venues are gone. Swapping a venue now means a new binary.
Nothing marks a venue dead.
registerreturns aLivenessflag, but no path outside videre's own tests sets it, so a wedged native venue cannot be quarantined and the registry keeps routing to it.Venue response bodies have no byte cap. The wasm memory limit used to bound them; they are now bounded only by the request timeout.
Decisions taken
The keeper store prefix changed from
watch:tocommitment:. No migration is written, and none is needed: there are no legacy rows in any live store. Confirmed by the operator.Both event triggers now set
resume = true, and so does ethflow-watcher's. A trigger without it re-opens at head, so every log mined during downtime was lost silently and permanently. Both keepers build their whole state from logs, so a restart at head loses any order registered while the engine was down.max_lookbackstays unset on all three, because a cap drops the oldest missed blocks, which is the loss being fixed. A first boot has no cursor and still starts at head, so this backfills a restart gap only.Replay is already idempotent and tested:
persist_commitmentoverwrites in place keeping the newest stamp, and ethflow-watcher short-circuits onobserved:{uid}. The two twap-monitor cursors are independent, so a backfilled removal can land before its own create and briefly resurrect a commitment; the next poll revertsSingleOrderNotAuthed, whichLegacyRevertAdapter::classifyfolds toInvalid, and the commitment drops.Chain-log durability
Two keys keep the keepers from going silently blind, and this PR sets both on every event trigger.
resume = truepersists a cursor, so a restart backfills the downtime gap instead of re-opening at head.start_blockseeds the first boot, which has no cursor to resume from. Without it a fresh daemon never learns of anything that happened before it ran, and a ComposableCoW conditional order can stay live for weeks. Blocks resolved from each deploy transaction:ComposableCoW is one CREATE2 address on every chain but a different block per chain, so mainnet is 17883049 when shepherd#657 makes that move. EthFlow has had several per-network deployments, so its block belongs to the pinned address and must be re-derived if that address changes.
The seed applies only while no cursor is stored. After the first committed chunk the store wins, so it is a one-time floor and not a rescan point.
max_lookbackstays unset, because a cap drops the oldest missed blocks, which is the loss being fixed.crates/shepherd-engine/tests/module_manifests.rspins both keys on every shipped event trigger. Dropping either still parses, still boots and still passes every other test in the repo, so nothing else would catch the regression.Pin bumps
nexum-runtimefd16db7to2ed882f, which is where thestart_blockkey comes from (nullislabs/nexum-runtime#381).videre66c8c3btofd8af02, its matching bump (nullislabs/videre-nexum-module#87).These move in lock-step by necessity: both repos pin the same
nexum-runtimerev, and a split pin resolves two copies of the crate whoseExtensionseam types do not match.Verification
just build-modules,just fmt,just lintandjust testall clean: 171 tests pass, 1 skipped.AI Assistance: Claude Code used for the migration across five parallel units and the reconciliation.