When crypto migrated to pmv2 v2 it renamed its wire source from the old latency-test value to crypto — which silently fail-closed PM until a matching pmv2_autotrade_sources row was seeded. Captures the root-cause gotcha and the reusable seed-migration fix.

For Agents

Discovered 2026-06-24 during a babylon watch tick. Root cause = PM resolves autotrade mode from the message body order.source, not the NATS subject. The fix is a one-row idempotent seed migration. This is the canonical example of the pmv2-source-id-3way-invariant-2026-06-23.

What Happened

  • crypto migrated to pmv2 v2 (their pin a4bec3a) and now emits a pmv2-contracts schema_2 EntryOrder on NATS subject pmv2.order.crypto.entry with source=crypto.
  • Previously the latency test used the source/subject crypto_shortterm_latency_test. So the wire source string changed.

Symptoms

  • Crypto orders arrive on pmv2.order.crypto.entry but PM never executes or dry_runs them.
  • No error, no crash — orders are silently Skipped (Mode::Off).

Root Cause (the gotcha)

PM’s order consumer (crates/position_manager/src/consumer.rs) resolves the autotrade mode by the message body field:

resolve_entry_mode(pool, &order.source)

It keys on order.source from the body, NOT the NATS subject token. So a producer renaming its source string silently breaks PM unless a matching pmv2_autotrade_sources.source_id row exists. The lookup misses → PM fail-closes to Mode::Off and Skips the order. No row, no error — just silence.

Fix (the reusable pattern)

Seed the source with an idempotent migration, default-disarmed:

  • File: supabase/migrations/20260624000000_pmv2_crypto_source.sql
  • Seeds source_id='crypto', enabled=TRUE, mode='off'
  • INSERT ... ON CONFLICT DO NOTHING (idempotent / re-runnable)
  • Committed + pushed to origin/main (pin 5badf4c).
  • Applied to the DB only via deploy’s gated supabase db push — not run ad-hoc.

Fix pattern for any new producer source

Whenever a producer adds or renames its wire source, add a one-row idempotent seed migration to pmv2_autotrade_sources with mode='off'. Default-disarmed = fail-closed safe; the operator arms it later.

Compatibility Notes

  • PM’s pinned pmv2-contracts (rev 4c7a54f) has SCHEMA_VERSION=2, so schema_2 EntryOrders pass the consumer’s schema_version gate. No contract bump needed.
  • No PM-side minimum-share floor exists — so size_shares=5 (Limit) is structurally fine. Quantization happens at execution, and the venue CLOB minimum applies there, not in PM.

Mode Semantics

pmv2_autotrade_sources.mode ∈ { off, dry_run, live } — there is no literal dry. Arming a source to dry_run is a deliberate operator (Andras) action; agents keep it off / fail-closed.