Flow
Checking a key that merges rows
1The clash case
Two real entries that share every part of the key.
2A tie-breaker
Something that tells those two entries apart.
3A tie-breaker that holds still
The tie-breaker should not change when nearby lists change.
4The saving side's rule
The unique rule where rows are saved must not be looser.
5Old rows
Rows saved under an older key shape will not match new ones.
Put the same promo on one receipt line twice, with two different amounts, and my vendor-credit pipeline could keep only one of them.
The pipeline reads store sales and turns each promo discount and each return into an event. Each event gets a key made from the receipt, and the app that saves events does an upsert on that key: new key, new row; known key, update the row. That way pulling the same sales twice doesn't double anything.
Two entries, one key
The key was receipt, promo and line. But one line can have the same promo twice, with two amounts. Both entries made the same key, so the upsert saw the second one as a repeat and kept one row. There was no error. The vendor total was just short.
The test for it is small: one receipt, one line, the same promo twice. It expects two events, both amounts, and the import counts to agree on two.
A number that depends on its neighbours
The first change added each entry's position to the key. The test passed, but that position came from one combined list. Likely receipt-level discounts go first, then each line's discounts, then, apparently, a stand-in entry when only a total exists. A discount's number depended on what came before it.
If that number ever moved between two pulls, the key would move too, and the upsert would add a second row. I didn't see that happen. I just didn't want to swap missing money for double money.
So each entry now gets a label for where it came from and its position inside that group, before anything is combined:
const key = [receiptId, promoId, origin, slot, lineId ?? "none"].join("/");
Two lines with one discount each now both get slot zero, and the line in the key keeps them apart.
The saving side
The app that saves events had its own key. It used the readable key first and the gateway's stable ID only as a backup. The readable key starts with the receipt number, and I'd guess two stores can share one. A new test sends two stores with the same readable key and expects two rows. The stable ID goes first now.
The stored ID changed shape more than once that day, so rows saved before won't line up with rows saved after. Sorting out those old windows is the next job.