Skip to content

cow: M4 follow-ups on the venue adapter, the order body and the keeper journal #569

Description

@mfw78

Successor to the delivered M4 umbrella #138. These are its residual review follow-ups plus two re-homed issues.

Follow-up epic for M4, opened after the eleven M4 cars (#462-#474) and #562 merged into dev/m1 at aec462a. Target: L3 cow-venue / composable-cow / modules, plus one L1 nexum-sdk primitive.

Seven items survive verification against the tree. Six issues were delivered by the cars and close without work, and two are re-homed out of the milestone.

The M5 three-repo split is now redone after these follow-ups land, so no item here carries a pre-cleave urgency argument. The forcing functions that remain are real: one live duplicate-order defect, one wire-format change that must happen before anything is released, and a file-ownership window on the module strategies that closes the moment the next car opens.

Scope

#538 keeper: make journal dedup durable across a post-submit record fault. Reserve/commit/release on the existing local-store verbs, no WIT change. Three sites, not two: the L2 sweep at crates/videre-sdk/src/keeper.rs:105-111, the L3 CoW sweep at crates/composable-cow/src/sweep.rs:133-157, and modules/examples/stop-loss/src/strategy.rs:80-111, which hand-rolls submitted:{id} against host.get/host.set and therefore inherits nothing from a Journal fix. Port stop-loss onto Journal in the same pass. The doc-tightening bullet is already landed by #473 and drops from scope.

#560 cow: make the already-held interpretation explicit at the type level. Split refusal into refusal_for_submit -> Refusal and refusal_for_read -> VenueError so a read path cannot hold an unresolved already-held meaning. Standalone PR against dev/m1; the #473 file lock is gone.

#559 cow: reconcile the submit receipt against the orderbook's stored order. Narrowed to the cheap option: assert the client-side derivation against the server UID on the Posted::Accepted branch at crates/cow-venue/src/adapter.rs:171/:184, plus the rustdoc caveat. Do not widen the status_with signature, and do not implement the review's original guard, which the body already retracts.

#545 cow-venue: reconsider the OrderBuilder typestate. Decided: OrderBuilder::new(sell, buy, valid_to) plus optional setters, dropping PhantomData, the four state markers and the per-state impls. Not the keep-and-document option, and not a Result-returning build.

#546 cow-venue: use alloy primitives instead of byte-array aliases. Supersede the body's ruint-direct plan with alloy-primitives = { features = ["borsh"] }, as #557 proved. The body's "regenerate the goldens if the encoding moves" is not conditional: ruint serializes Uint little-endian (as_le_slice), the current U256 = [u8; 32] fields are big-endian, so sell_amount/buy_amount/fee_amount flip. Both crates/cow-venue/tests/vectors/cow-intent-body.json and cow-header-goldens.json regenerate, and intent_id changes with them, so any live submitted: journal key rotates. Settle the optional = true and no_std questions in the same pass.

#398 (M4 sliver only) cow: model Verdict::Post::next_poll_timestamp as Option<u64> instead of the 0 sentinel. The rest of #398, the wire swap and the LegacyRevertAdapter deletion, is gated on a third-party deployment and re-homes to M8.

#522 examples: adopt the keeper vernacular for the submitter modules. Rescoped to two modules, twap-monitor and stop-loss. ethflow-watcher is now observe-only (modules/ethflow-watcher/src/lib.rs:8, sole venue verb venue.observe at strategy.rs:174), so by the issue's own criterion it joins balance-tracker and price-alert in the exclude column. The body's claim that twap-monitor's header already reads "keeper module" is false; it reads "Pure strategy logic for the twap-monitor module".

Order

Wave 1: #538 and #560 in parallel. Disjoint crates.

Wave 2: #559 and #398-sliver in parallel. #559 follows #560 in adapter.rs; the sliver follows #538 in composable-cow.

Wave 3: #545 alone.

Wave 4: #546 alone, after #545 by maintainer decision so order.rs is reworked once.

Wave 5: #522 alone, last. It renames the two files every earlier wave edits.

Re-homed

nullislabs/videre-nexum-module#12 venue: chain-qualify the venue id, to M6. The body's premise is wrong: crates/videre-host/src/registry.rs:355 already returns DuplicateVenue and nexum-runtime/src/supervisor.rs:933 fails boot on it, so the fallback acceptance criterion is met. The host keys on the adapter's manifest name, not on CowVenue::ID, so two chains can already register distinct ids by manifest. What remains is a Venue::ID and typed-client change, and the second venue is what proves whether it is needed.

#398 (the wire swap), to M8. Blocked on the nullisLabs fork being deployed somewhere.

Closed on verification

#399, #402, #121, #75, #48 are delivered on the tree. #138 closes once #398 is detached and re-milestoned.

Binding decisions, 2026-07-23

These supersede the plan above where they differ.

stop-loss is deleted, not ported. It is subsumed by the generic ComposableCoW monitor, tracked as #568, which lands first so no later wave touches a file about to be removed. Two consequences: #538 drops its stop-loss Journal port and covers only the L2 keeper sweep and the L3 CoW sweep; #522 rescopes to twap-monitor alone, since ethflow-watcher is observe-only and stop-loss is gone. Deleting the module removes the five-touch collision seam the M4 and M3 waves shared.

The #398 sliver is #567 and rides in wave 2 with the #538 pass. The rest of #398, the wire swap and the LegacyRevertAdapter deletion, stays in M4 labelled blocked rather than moving to M8, so the milestone shows the full picture; it cannot close until the fork is deployed. This corrects the plan's re-homing of that arm to M8.

Two unmilestoned issues fold in here on the same adapter seam: #491 (restore or document the dropped empty-receipt validation) beside #559 and #560, and #496 (type the retry classifier boundary with OrderbookApiErrorType) before wave 3, since it is a fifth writer on crates/cow-venue/src/adapter.rs.

#546 keeps the accepted wire break. The encoding moves, both vector files regenerate, intent_id rotates and any in-flight order resubmits once on the first tick after deploy. #538 lands first so that resubmit is absorbed by the reserve/commit path rather than by luck, and nothing may be deployed when it lands.

Revised order

Wave 0: #568, delete stop-loss.

Wave 1: #538 and #560 in parallel.

Wave 2: #559, #491 and #567 (the sliver). #559 and #491 follow #560 in adapter.rs; the sliver follows #538 in composable-cow.

Wave 3: #496 then #545, both on cow-venue. #496 types the classifier boundary, #545 replaces the OrderBuilder typestate.

Wave 4: #546 alone, the wire break.

Wave 5: #522 alone, last, the twap-monitor rename.

Metadata

Metadata

Assignees

No one assigned

    Labels

    component/cow-integrationshepherd:cow / cow-api order submissionepicTracks a larger initiative.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions