Task B1 on autotrade-phase2-firstmover: two fixes in sources.rs that make signal_id byte-identical regardless of which producer (cron or future on-chain watcher) mints it.
Problem
The engine deduplicates trade signals via ON CONFLICT (dedup_key) in the DB. dedup_key is derived from signal_id, which is minted by cohort_signal_id. For cron and watcher to deduplicate the same logical entry, they must produce the same signal_id from the same underlying position data.
Two bugs broke that parity:
1. No lowercasing before orders::dedup_key
cohort_signal_id passed origin_ref (wallet address), condition_id, and instance verbatim to orders::dedup_key. On-chain addresses are always lowercase hex. The cron side can carry checksummed (mixed-case) wallet addresses from the scored cohort. The same wallet produces two different strings: 0xAbCd… vs 0xabcd…, yielding different signal_ids for the same trade intent.
2. instance used event_slug (unstable Option<String>)
enter_signal passed p.event_slug.unwrap_or(condition_id) as the instance argument. event_slug is an Option<String>:
- If the cron populates it, the instance includes the slug.
- If a future watcher path omits it (no slug available at detection time), the instance falls back to
condition_id.
These two paths produce different instance values → different signal_ids for the same position.
Fixes
Fix 1 — lowercase canonicalization in cohort_signal_id
All three inputs are lowercased before being passed to orders::dedup_key:
orders::dedup_key(&origin_ref.to_lowercase(), action, &condition_id.to_lowercase(), &instance.to_lowercase())On-chain addresses (always lowercase hex) and cron checksummed addresses now collapse to the same key.
Fix 2 — instance = condition_id in enter_signal
// before
instance: p.event_slug.clone().unwrap_or_else(|| p.condition_id.clone())
// after
instance: p.condition_id.clone()condition_id is always present and matches the on-chain identifier. It is stable across cron and watcher paths. The event_slug was a volatile fallback that broke cross-producer parity and introduced an Option into a field that should be deterministic.
One breaking test update required
enter_signal_freezes_reference_price_and_fills_fieldspreviously passed"evt"as the expectedinstance. Updated to"0xcond"to match the newinstance = condition_idbehavior.
Tests
| Test | What it verifies |
|---|---|
cohort_signal_id_is_canonical_lowercase_instance_condition | Mixed-case origin_ref, condition_id, instance inputs → fully lowercase signal_id output |
watcher_and_cron_signal_ids_byte_match_for_same_entry | enter_signal (cron path) and direct cohort_signal_id with lowercase on-chain inputs produce byte-identical strings |
Gate: all tests passing, gate=0.
For Agents
The byte-identity of
signal_idacross producers is the ONLY mechanism that prevents duplicate live orders when both cron and watcher detect the same cohort entry. The DBON CONFLICT (dedup_key) DO NOTHINGis the backstop, but correct dedup requires identical keys. Any future producer must: (1) lowercaseorigin_ref(wallet),condition_id, andinstancebefore callingcohort_signal_id, and (2) usecondition_id— notevent_slugor any other Optional field — as theinstancediscriminator.
Key Design Decision
event_slug was removed from the instance computation because:
- It is
Option<String>— absent on the watcher detection path (no CLOB context at detection time). - Even when present, it can vary (e.g., event slugs can be updated or aliased).
condition_idis the authoritative on-chain market identifier and is always present on both cron and watcher paths.
Branch / File
- Branch:
autotrade-phase2-firstmover - File:
crates/polymarket-fetch/src/pmv2/autotrade/sources.rs
Related
- autotrade-sources-signal-emitter — initial
sources.rsTask 6 implementation (signal structure,enter_signalfactory,autotrade_activeguard) - cohort-firstmover-nats-migration — NATS migration that made cross-producer parity critical (watcher publishes, cron publishes, both must dedup to one order)
- autotrade-engine-impl — engine
ingest_entry+ON CONFLICT (dedup_key) DO NOTHINGbackstop - autotrade-signal-type —
TradeSignalstruct anddedup_key()derivation