The monolith feature where pasting a polymarket.com/ link makes the bot reply with the cohort holders for that specific bet (plus a category-consensus fallback when no high-edge signal exists) was never ported to pmv2. Restored 2026-06-24 over the live NATS query channel — same plumbing as the just-restored /topbets / /consensus / /filters. This is a 1:1 verbatim port from the monolith, reviewed (fidelity + 4 adversarial lenses) and gated green.

For Agents — state at a glance

  • Contract: pmv2-contracts c6b3a15 — additive QueryKind::BetInspect { url }, serde back-compat (internally-tagged + flattened, same discriminator pattern as the other QueryKind variants).
  • Responder: cohort_algo 1d573b8 (= prior 299cf1a + the render_bet_inspect handler; still carries the rustls fix from pmv2-self-watch-rustls-cryptoprovider-crashloop).
  • Trigger: telegram_connector detects a non-slash message containing polymarket.com/ and publishes BetInspect { url }. NO new NATS subjects/permits — reuses query.request / query.results.
  • Connector arm pending: non-slash detection = @pmv2_telegram (babylon task #517). Cohort side is shipped + gated.
  • Deploy order: ship cohort 1d573b8 before/with the connector. An unknown betinspect reaching an old cohort is a fail-safe skip (no crash).

Data flow

Identical plumbing to query-commands-restoration — this is just one more QueryKind variant on the existing channel:

  1. telegram_connector detects a non-slash message body containing polymarket.com/ → publishes QueryKind::BetInspect { url } on query.request.
  2. cohort query-responderrender_bet_inspect:
    • score_bet(url) — bet-scoped holder lookup (the specific condition/outcome of the pasted link) + gauge body formatter.
    • If !has_high_edge_signalresolve_fallback_category + category_consensus_section → appended fallback.
  3. Publishes QueryReply { rendered } on query.results.
  4. connector posts rendered as HTML.

This mirrors the monolith’s MessageAction::BetLink auto-analyzer (see pmv2-bet-link-analyzer-flow) — same two empty-states, same edge-scored cohort, same category-consensus fallback semantics — now relocated behind the query channel instead of running in-process in the bot.

Notable engineering

  • DRY shared SQL const — extracted BET_HOLDER_SELECT (a single SELECT body) shared by load_bet_holders + all_cohort_holders, with a byte-identity guard test so the two callers can never silently diverge.
  • Backpressure semaphore (cap 4) added to the query-responder. The monolith had one; the earlier query-channel port had dropped it — restored here so concurrent bet-inspect/topbets/consensus requests can’t unbounded-fan-out the DB.

Deferred follow-up

Advisory-lock dedup in run_and_persist — duplicate same-category consensus recompute is currently wasted work (two requests for the same category recompute independently). Not corruption, just redundant load. Also affects /consensus. Wrap recompute in a per-category Postgres advisory lock so the second caller waits on / reuses the first’s result.

Review

1:1 verbatim port from the monolith. Reviewed for fidelity plus 4 adversarial lenses: DB-schema, concurrency, DRY/SOLID, regression/serde. Gate green.

Pins

CratePinNote
pmv2-contractsc6b3a15additive QueryKind::BetInspect { url }, serde back-compat
cohort_algo1d573b8= 299cf1a + render_bet_inspect; carries rustls fix