In the democracycraft prod economy DB, all 787,092 chestshop_sale rows have txn_id = NULL (SELECT COUNT(*) WHERE txn_id IS NOT NULL → 0). So the analytics sales table is completely unlinked from the authoritative ledger — you can't drill from a recorded sale to the money movement, reconcile a sale's total_price against the ledger posting, or join chestshop_sale → ledger_txns. The idx_cs_txn index is therefore indexing an all‑NULL column (dead weight). Assume statecraft is the same (same capture path).
The plumbing already exists end‑to‑end on the Treasury side:
ChestShopSaleRecord.txnId (treasury-api/.../market/ChestShopSaleRecord.java) — documented "nullable if unlinkable".MarketApiImpl.recordSale passes s.txnId(); ChestShopMarketMapper.insertSale writes txn_id.The gap is the caller: ChestShop‑3's MarketListener/MarketHook builds the ChestShopSaleRecord with txnId = null. Under the current Vault subtract/deposit two‑hop bridge (see PAR‑158) a sale isn't a single transfer — it's two SYSTEM‑bridged txns (buyer→ChestShop System, System→seller) — so there's no single clean txn id to attach, which is exactly why it's always left null.
Best done as part of / right after PAR‑158 (switch player‑shop sales to a native TreasuryApi.transfer(buyer→seller)): thread the transfer's resulting txn_id into the ChestShopSaleRecord so recordSale persists it. For admin/server shops (single faucet/sink leg, no buyer→seller), link that leg's txn id.
Sale→ledger drill‑through, sale‑vs‑posting reconciliation, makes idx_cs_txn useful (or lets us drop it), and gives PAR‑159's two‑hop pairing a direct key instead of fuzzy matching.
txn_id that matches the ledger transfer; admin‑shop sales link their faucet/sink txn.chestshop_sale.txn_id → ledger_txns.txn_id.Backfilling the existing ~787K NULL rows is the sibling task (see linked backfill issue). Related: PAR‑158 (forward single‑transfer fix), PAR‑159 (two‑hop collapse in explorer views).
Sibling backfill task for the existing ~787K NULL rows: PAR-235. Both share the ledger‑pairing logic with PAR-159 and are gated by the PAR-158 single‑transfer fix.