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
cryptomigrated to pmv2 v2 (their pina4bec3a) and now emits apmv2-contractsschema_2EntryOrderon NATS subjectpmv2.order.crypto.entrywithsource=crypto.- Previously the latency test used the source/subject
crypto_shortterm_latency_test. So the wiresourcestring changed.
Symptoms
- Crypto orders arrive on
pmv2.order.crypto.entrybut PM never executes ordry_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(pin5badf4c). - 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 topmv2_autotrade_sourceswithmode='off'. Default-disarmed = fail-closed safe; the operator arms it later.
Compatibility Notes
- PM’s pinned
pmv2-contracts(rev4c7a54f) hasSCHEMA_VERSION=2, so schema_2EntryOrders pass the consumer’sschema_versiongate. 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.
Related
- position_manager — service overview
- pmv2-source-id-3way-invariant-2026-06-23 — the producer/DB/subject 3-way invariant this is an instance of