From review of #466 (comment r3616645881). Verified against the end-of-train tip (feat/m5-git-tag-pins-umbrella-patch, 24c4d9e).
intent_id hashes the whole encoded CowIntentBody, and for the Signed variant that body carries signature: Vec<u8>, so the signature bytes are part of the submitted: journal key.
At the tip the sweep builds the key from a freshly assembled signed body (composable-cow/src/sweep.rs:120) and hashes it through cow_venue::intent_id (cow-venue/src/client.rs:38), which wraps submission_key(&CowVenue::ID, &body.to_bytes()?). The SignedOrder borsh encoding at cow-venue/src/order.rs:267 includes the signature field.
The consequence is that the key scopes to "this exact signed payload", not "this economic order": two attempts at the same order under different signature bytes produce different keys and therefore do not dedup against each other.
Why this is not currently a correctness bug
Two independent reasons, both verified at the tip rather than assumed.
The keeper never signs. submit_ready receives the signature from source.poll's Verdict::Post { order, signature, .. } (composable-cow/src/sweep.rs:60), so the bytes are read from the conditional-order source rather than generated per attempt, and a restart re-reads the same deterministic proof for the same order and params.
The venue absorbs a mis-keyed retry anyway. The orderbook's identity for an order is signature-independent: assembly::order_uid is order.uid(&chain.settlement_domain(), owner). A second attempt on the same economic order therefore hits the duplicate path, classification::is_already_submitted maps the error type to Refusal::AlreadyHeld (cow-venue/src/adapter.rs:391), post_order returns Posted::AlreadyHeld (:325), and the adapter answers SubmitOutcome::Accepted with the derived canonical uid (:172). The cost is one redundant POST, not a duplicate order.
What to decide
Option A, document the scope. State on intent_id that the key covers the exact signed payload and that cross-signature dedup relies on the venue's idempotent duplicate response. Cheapest, and it matches how the code actually behaves today.
Option B, key on the economic order. Hash (venue, owner, order) and exclude the signature, so the key means "this order" independent of how it was authorised.
Option A is the recommendation. Option B is only clearly better if a future venue is added whose duplicate response is not idempotent, which is the latent hazard worth naming either way.
Acceptance criteria
intent_id's rustdoc states exactly what the key scopes to, so a later venue author does not assume order-level dedup.
If Option B is taken instead, the key excludes signature bytes and the existing determinism and body-scoping tests are extended to cover two distinct signatures over one order.
intent_idhashes the whole encodedCowIntentBody, and for theSignedvariant that body carriessignature: Vec<u8>, so the signature bytes are part of thesubmitted:journal key.At the tip the sweep builds the key from a freshly assembled signed body (
composable-cow/src/sweep.rs:120) and hashes it throughcow_venue::intent_id(cow-venue/src/client.rs:38), which wrapssubmission_key(&CowVenue::ID, &body.to_bytes()?). TheSignedOrderborsh encoding atcow-venue/src/order.rs:267includes the signature field.The consequence is that the key scopes to "this exact signed payload", not "this economic order": two attempts at the same order under different signature bytes produce different keys and therefore do not dedup against each other.
Why this is not currently a correctness bug
Two independent reasons, both verified at the tip rather than assumed.
The keeper never signs.
submit_readyreceives the signature fromsource.poll'sVerdict::Post { order, signature, .. }(composable-cow/src/sweep.rs:60), so the bytes are read from the conditional-order source rather than generated per attempt, and a restart re-reads the same deterministic proof for the same order and params.The venue absorbs a mis-keyed retry anyway. The orderbook's identity for an order is signature-independent:
assembly::order_uidisorder.uid(&chain.settlement_domain(), owner). A second attempt on the same economic order therefore hits the duplicate path,classification::is_already_submittedmaps the error type toRefusal::AlreadyHeld(cow-venue/src/adapter.rs:391),post_orderreturnsPosted::AlreadyHeld(:325), and the adapter answersSubmitOutcome::Acceptedwith the derived canonical uid (:172). The cost is one redundant POST, not a duplicate order.What to decide
Option A, document the scope. State on
intent_idthat the key covers the exact signed payload and that cross-signature dedup relies on the venue's idempotent duplicate response. Cheapest, and it matches how the code actually behaves today.Option B, key on the economic order. Hash
(venue, owner, order)and exclude the signature, so the key means "this order" independent of how it was authorised.Option A is the recommendation. Option B is only clearly better if a future venue is added whose duplicate response is not idempotent, which is the latent hazard worth naming either way.
Acceptance criteria
intent_id's rustdoc states exactly what the key scopes to, so a later venue author does not assume order-level dedup.If Option B is taken instead, the key excludes signature bytes and the existing determinism and body-scoping tests are extended to cover two distinct signatures over one order.