Reframed the v1 self-wallet consensus watchers as first-mover OVERLAP detection on the pmv2 pipeline: the cohort watcher emits an append-only ENTER/EXIT event feed, and a cursor-based join against the operator’s own book raises CONFIRM / FOLLOW_OUT / AGAINST alerts annotated with the trader’s confidence score and avg won/lost bet sizes. Shipped 2026-06-02 on main as commit 4794a76 (+932/−2381 lines); migration 20260603000000_pmv2_overlap applied to prod.

For Agents

  • Source of truth for cohort ENTER/EXIT: run_pmv2_positions → append-only pmv2_position_events.
  • Detection/logging is DECOUPLED from the copy-enabled gate; only the paper-copy ACTION is gated on pmv2_copy_config.enabled. Events populate even with the copy lane OFF.
  • New module: overlap_watcher.rs. Cursor key 'overlap_watcher' in pm_detection_cursors.
  • GO-LIVE: overlap / new-position / self-trade alerts are NOT default-off — they fire the moment the binary deploys. Set pmv2_copy_config.score_threshold > 0 (e.g. 0.03–0.05) to avoid noise; default 0 qualifies ALL scored cohort traders.

What Shipped

The v1 self-wallet consensus watchers (change_detection.rs, consensus_participant_changes.rs) were retired and replaced by first-mover overlap detection layered on the pmv2 pipeline. Rather than asking “do my watched wallets agree on a market” (consensus, previously rejected as an edge), the system now asks “did a high-confidence cohort trader just take or leave a position that overlaps my own book, and on which side?”

  • Commit 4794a76 on main, +932 / −2381 lines (net deletion — large consensus surface removed).
  • Migration 20260603000000_pmv2_overlap applied to production.

Architecture — Approach A (event-log join)

The design is a decoupled producer/consumer split: the cohort watcher produces a denormalized event feed; the overlap watcher consumes it via a cursor and joins against the operator’s book.

1. Cohort watcher as source of truth

run_pmv2_positions is now authoritative for cohort ENTER/EXIT and writes an append-only pmv2_position_events feed.

Detection decoupled from the copy gate

A new predicate should_detect(prior_len) = prior_len > 0 controls whether detection + event logging run. This is independent of pmv2_copy_config.enabled — only the paper-copy ACTION stays gated. Consequence: the event feed populates (and overlap alerts fire) even while the copy lane is OFF.

2. EXIT detection + current-state snapshot mirror

  • New detect_exited adds cohort EXIT detection.
  • The cohort snapshot became a true current-state mirror: each cycle DELETEs cohort positions absent from the fresh fetch.
  • CRITICAL safety scoping: the delete is restricted to wallets that successfully fetched. fetch_positions now returns (rows, succeeded: HashSet<...>). A timed-out wallet therefore cannot fire false EXITs or mass-delete the snapshot.

Why the succeeded set matters

Without scoping the delete to wallets that actually returned data, a single timed-out fetch would look like “this wallet exited every position”, firing bogus FOLLOW_OUT alerts and wiping the snapshot. Relates to the proxied-client silent-hang class of bug — timeouts must never be read as “empty result”.

3. Denormalized events

Events are denormalized at log time with run_id, trader_score, avg_won_bet_size, avg_lost_bet_size. This avoids a fragile “which run’s score applies” join and lets events outlive cohort membership (a trader can drop out of the cohort and the historical event still carries its score context).

4. overlap_watcher.rs — the join

Cursor-based (key 'overlap_watcher' in pm_detection_cursors) join of the event feed against the operator’s own book:

Cohort eventOperator sideAlert
ENTERsame sideCONFIRM
EXITsame sideFOLLOW_OUT
ENTERopposite sideAGAINST
EXITopposite side(intentionally NOT alerted)
  • Dedup via pm_overlap_alerts UNIQUE(event_id, self_wallet).
  • Alerts include the trader’s confidence score + avg won/lost bet sizes (explicit user requirement).

Operator Side

  • New-position alert: prior-vs-current self-snapshot diff via repo::self_new_positions, scoped to pm_self_wallets + the latest two self_snapshot runs, with the first run suppressed (no spurious “everything is new” on cold start). Routed to the ACTIVE notifier.
  • Notifier routing: self-sync start/complete summaries stay on the SILENT notifier; run_self_sync_for_scheduler now takes BOTH notifiers so the new-position alert can go active while housekeeping stays quiet.
  • self_trade_changes repointed off the stale pm_trades table to the live DataApiClient::trades per self-wallet.

Removals

  • change_detection.rs and consensus_participant_changes.rs deleted.
  • Consensus-only repo/notifier surface removed.
  • Pipeline + bot daemon repointed: the overlap watcher replaces change-detection + participant-changes everywhere.

Migration 20260603000000_pmv2_overlap

Forward ALTERs on the LIVE pmv2_position_events table:

  • Dropped the per-key UNIQUE constraint — the table is now an append-only lifecycle log (enter → exit → re-enter must all coexist).
  • kind CHECK changed to ('ENTERED', 'EXITED').
  • Added columns run_id, trader_score, avg_won_bet_size, avg_lost_bet_size.
  • Created pm_overlap_alerts (with UNIQUE(event_id, self_wallet)).

Gotcha — truncated Postgres constraint name

The auto-generated unique constraint name was TRUNCATED to ..._outcome_inde_key (NOT ..._index_key). The DROP CONSTRAINT had to use the real truncated name. Always look up actual constraint names via pg_constraint before issuing a DROP — do not assume the name from the column list.

Go-Live Behavior

Not default-off

Unlike the paper-copy lane, the overlap / new-position / self-trade alerts go LIVE the moment the binary deploys. The overlap gate uses pmv2_copy_config.score_threshold, which defaults to 0 → ALL scored cohort traders qualify → very noisy.

Recommendation: set score_threshold > 0 (e.g. 0.030.05) before or right after deploy. The paper-copy lane stays independently enabled = false.

Known Loose End / Follow-up

The self-status CLI and the bot /wallets command still render v1 consensus alignment from the frozen pm_selected_wallets (safe-but-stale; no runtime error). The consensus chain — fetch_consensus_alignment, fetch_raw_watched_consensus, ConsensusAlignment, classify_consensus, fetch_safe_bets_by_market — was intentionally KEPT because deleting it breaks self-status. Reframe or drop that display in a future pass.

Verification

  • Built via subagent-driven development (12 tasks), with per-task gates, spec/quality reviews, and a final integration review.
  • 0 test failures.
  • clippy: only the 3 pre-existing scoring.rs warnings.
  • fmt clean.