diff --git a/docs/evidence/sold-ab-2026-08-18/A_highest.json b/docs/evidence/sold-ab-2026-08-18/A_highest.json new file mode 100644 index 0000000..5382164 --- /dev/null +++ b/docs/evidence/sold-ab-2026-08-18/A_highest.json @@ -0,0 +1,114 @@ +{ + "env": { + "OPENFUT_FIFA17_SOLD_COINS_PROCESSED": "0", + "OPENFUT_FIFA17_SOLD_EXPERIMENT": "highest" + }, + "surfaces": { + "counts": { + "body": { + "count": 0, + "maxAuctionsAllowed": 100, + "offered": 0, + "selling": 0, + "sold": 1 + }, + "sha256": "766b1fcc4ca47442e6f570c81583840b6ae8df9cbeae52473b3c100ca881e206", + "status": 200 + }, + "tradePile": { + "body": { + "auctionInfo": [ + { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143, + "duplicateItemIdList": [], + "total": 1 + }, + "sha256": "caecc67a615ddd0f209b36c7c8ea6a04e0002552d9a42b8eeca20df84feedf3c", + "status": 200 + }, + "trade_status_filtered": { + "body": { + "auctionInfo": [ + { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143 + }, + "sha256": "0107bf70809cce10fa5fa0f7f4e196f7ffdd6def1b9f63407eeffc7e66f35855", + "status": 200 + }, + "trade_status_unfiltered": { + "body": { + "auctionInfo": [ + { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143 + }, + "sha256": "0107bf70809cce10fa5fa0f7f4e196f7ffdd6def1b9f63407eeffc7e66f35855", + "status": 200 + } + } +} \ No newline at end of file diff --git a/docs/evidence/sold-ab-2026-08-18/B_buyNow.json b/docs/evidence/sold-ab-2026-08-18/B_buyNow.json new file mode 100644 index 0000000..a70272e --- /dev/null +++ b/docs/evidence/sold-ab-2026-08-18/B_buyNow.json @@ -0,0 +1,114 @@ +{ + "env": { + "OPENFUT_FIFA17_SOLD_COINS_PROCESSED": "0", + "OPENFUT_FIFA17_SOLD_EXPERIMENT": "buyNow" + }, + "surfaces": { + "counts": { + "body": { + "count": 0, + "maxAuctionsAllowed": 100, + "offered": 0, + "selling": 0, + "sold": 1 + }, + "sha256": "766b1fcc4ca47442e6f570c81583840b6ae8df9cbeae52473b3c100ca881e206", + "status": 200 + }, + "tradePile": { + "body": { + "auctionInfo": [ + { + "bidState": "buyNow", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143, + "duplicateItemIdList": [], + "total": 1 + }, + "sha256": "0b6e5cc5ab58fdbdd76586b1cff685a562e669fd37750cbd1397db9ee9e97407", + "status": 200 + }, + "trade_status_filtered": { + "body": { + "auctionInfo": [ + { + "bidState": "buyNow", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143 + }, + "sha256": "fab8d43b7ac0f0e09d6902155f7c5f6d91e57b4b595ec8f6093a5a9dcb02bcdb", + "status": 200 + }, + "trade_status_unfiltered": { + "body": { + "auctionInfo": [ + { + "bidState": "buyNow", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143 + }, + "sha256": "fab8d43b7ac0f0e09d6902155f7c5f6d91e57b4b595ec8f6093a5a9dcb02bcdb", + "status": 200 + } + } +} \ No newline at end of file diff --git a/docs/evidence/sold-ab-2026-08-18/C_cp0.json b/docs/evidence/sold-ab-2026-08-18/C_cp0.json new file mode 100644 index 0000000..5382164 --- /dev/null +++ b/docs/evidence/sold-ab-2026-08-18/C_cp0.json @@ -0,0 +1,114 @@ +{ + "env": { + "OPENFUT_FIFA17_SOLD_COINS_PROCESSED": "0", + "OPENFUT_FIFA17_SOLD_EXPERIMENT": "highest" + }, + "surfaces": { + "counts": { + "body": { + "count": 0, + "maxAuctionsAllowed": 100, + "offered": 0, + "selling": 0, + "sold": 1 + }, + "sha256": "766b1fcc4ca47442e6f570c81583840b6ae8df9cbeae52473b3c100ca881e206", + "status": 200 + }, + "tradePile": { + "body": { + "auctionInfo": [ + { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143, + "duplicateItemIdList": [], + "total": 1 + }, + "sha256": "caecc67a615ddd0f209b36c7c8ea6a04e0002552d9a42b8eeca20df84feedf3c", + "status": 200 + }, + "trade_status_filtered": { + "body": { + "auctionInfo": [ + { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143 + }, + "sha256": "0107bf70809cce10fa5fa0f7f4e196f7ffdd6def1b9f63407eeffc7e66f35855", + "status": 200 + }, + "trade_status_unfiltered": { + "body": { + "auctionInfo": [ + { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143 + }, + "sha256": "0107bf70809cce10fa5fa0f7f4e196f7ffdd6def1b9f63407eeffc7e66f35855", + "status": 200 + } + } +} \ No newline at end of file diff --git a/docs/evidence/sold-ab-2026-08-18/D_cp1.json b/docs/evidence/sold-ab-2026-08-18/D_cp1.json new file mode 100644 index 0000000..8ef51a8 --- /dev/null +++ b/docs/evidence/sold-ab-2026-08-18/D_cp1.json @@ -0,0 +1,114 @@ +{ + "env": { + "OPENFUT_FIFA17_SOLD_COINS_PROCESSED": "1", + "OPENFUT_FIFA17_SOLD_EXPERIMENT": "highest" + }, + "surfaces": { + "counts": { + "body": { + "count": 0, + "maxAuctionsAllowed": 100, + "offered": 0, + "selling": 0, + "sold": 1 + }, + "sha256": "766b1fcc4ca47442e6f570c81583840b6ae8df9cbeae52473b3c100ca881e206", + "status": 200 + }, + "tradePile": { + "body": { + "auctionInfo": [ + { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 1, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143, + "duplicateItemIdList": [], + "total": 1 + }, + "sha256": "6e228a34b5d7fac6bf70710cbd4ca35b58740e04ae455ffeee04b6ce7faab8e5", + "status": 200 + }, + "trade_status_filtered": { + "body": { + "auctionInfo": [ + { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 1, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143 + }, + "sha256": "c1557013bf20285d0512ca0d055a16df8036260559d640da11102a2379ac5986", + "status": 200 + }, + "trade_status_unfiltered": { + "body": { + "auctionInfo": [ + { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 1, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143 + }, + "sha256": "c1557013bf20285d0512ca0d055a16df8036260559d640da11102a2379ac5986", + "status": 200 + } + } +} \ No newline at end of file diff --git a/docs/evidence/sold-ab-2026-08-18/differential.json b/docs/evidence/sold-ab-2026-08-18/differential.json new file mode 100644 index 0000000..b9d8a6a --- /dev/null +++ b/docs/evidence/sold-ab-2026-08-18/differential.json @@ -0,0 +1,509 @@ +{ + "counts_states": { + "S1_zero_active_one_sold": { + "count": 0, + "maxAuctionsAllowed": 100, + "offered": 0, + "selling": 0, + "sold": 1 + }, + "S2_one_active_one_sold_count_active": { + "count": 1, + "maxAuctionsAllowed": 100, + "offered": 0, + "selling": 1, + "sold": 1 + }, + "S2_one_active_one_sold_count_membership": { + "count": 2, + "maxAuctionsAllowed": 100, + "offered": 0, + "selling": 1, + "sold": 1 + } + }, + "diffs": { + "orthogonal_coinsprocessed": [ + { + "a": 0, + "b": 1, + "kind": "VALUE_MISMATCH", + "path": "auctionInfo[0].coinsProcessed" + } + ], + "primary_bidstate": [ + { + "a": "highest", + "b": "buyNow", + "kind": "VALUE_MISMATCH", + "path": "auctionInfo[0].bidState" + } + ] + }, + "generated": "2026-08-18T02:19:25Z", + "settlement": { + "buyer": 19850, + "fee": 7, + "instances": 1, + "owner": "club-buyer-b", + "seller": 1143 + }, + "variants": { + "A_highest": { + "env": { + "OPENFUT_FIFA17_SOLD_COINS_PROCESSED": "0", + "OPENFUT_FIFA17_SOLD_EXPERIMENT": "highest" + }, + "surfaces": { + "counts": { + "body": { + "count": 0, + "maxAuctionsAllowed": 100, + "offered": 0, + "selling": 0, + "sold": 1 + }, + "sha256": "766b1fcc4ca47442e6f570c81583840b6ae8df9cbeae52473b3c100ca881e206", + "status": 200 + }, + "tradePile": { + "body": { + "auctionInfo": [ + { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143, + "duplicateItemIdList": [], + "total": 1 + }, + "sha256": "caecc67a615ddd0f209b36c7c8ea6a04e0002552d9a42b8eeca20df84feedf3c", + "status": 200 + }, + "trade_status_filtered": { + "body": { + "auctionInfo": [ + { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143 + }, + "sha256": "0107bf70809cce10fa5fa0f7f4e196f7ffdd6def1b9f63407eeffc7e66f35855", + "status": 200 + }, + "trade_status_unfiltered": { + "body": { + "auctionInfo": [ + { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143 + }, + "sha256": "0107bf70809cce10fa5fa0f7f4e196f7ffdd6def1b9f63407eeffc7e66f35855", + "status": 200 + } + } + }, + "B_buyNow": { + "env": { + "OPENFUT_FIFA17_SOLD_COINS_PROCESSED": "0", + "OPENFUT_FIFA17_SOLD_EXPERIMENT": "buyNow" + }, + "surfaces": { + "counts": { + "body": { + "count": 0, + "maxAuctionsAllowed": 100, + "offered": 0, + "selling": 0, + "sold": 1 + }, + "sha256": "766b1fcc4ca47442e6f570c81583840b6ae8df9cbeae52473b3c100ca881e206", + "status": 200 + }, + "tradePile": { + "body": { + "auctionInfo": [ + { + "bidState": "buyNow", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143, + "duplicateItemIdList": [], + "total": 1 + }, + "sha256": "0b6e5cc5ab58fdbdd76586b1cff685a562e669fd37750cbd1397db9ee9e97407", + "status": 200 + }, + "trade_status_filtered": { + "body": { + "auctionInfo": [ + { + "bidState": "buyNow", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143 + }, + "sha256": "fab8d43b7ac0f0e09d6902155f7c5f6d91e57b4b595ec8f6093a5a9dcb02bcdb", + "status": 200 + }, + "trade_status_unfiltered": { + "body": { + "auctionInfo": [ + { + "bidState": "buyNow", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143 + }, + "sha256": "fab8d43b7ac0f0e09d6902155f7c5f6d91e57b4b595ec8f6093a5a9dcb02bcdb", + "status": 200 + } + } + }, + "C_cp0": { + "env": { + "OPENFUT_FIFA17_SOLD_COINS_PROCESSED": "0", + "OPENFUT_FIFA17_SOLD_EXPERIMENT": "highest" + }, + "surfaces": { + "counts": { + "body": { + "count": 0, + "maxAuctionsAllowed": 100, + "offered": 0, + "selling": 0, + "sold": 1 + }, + "sha256": "766b1fcc4ca47442e6f570c81583840b6ae8df9cbeae52473b3c100ca881e206", + "status": 200 + }, + "tradePile": { + "body": { + "auctionInfo": [ + { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143, + "duplicateItemIdList": [], + "total": 1 + }, + "sha256": "caecc67a615ddd0f209b36c7c8ea6a04e0002552d9a42b8eeca20df84feedf3c", + "status": 200 + }, + "trade_status_filtered": { + "body": { + "auctionInfo": [ + { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143 + }, + "sha256": "0107bf70809cce10fa5fa0f7f4e196f7ffdd6def1b9f63407eeffc7e66f35855", + "status": 200 + }, + "trade_status_unfiltered": { + "body": { + "auctionInfo": [ + { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143 + }, + "sha256": "0107bf70809cce10fa5fa0f7f4e196f7ffdd6def1b9f63407eeffc7e66f35855", + "status": 200 + } + } + }, + "D_cp1": { + "env": { + "OPENFUT_FIFA17_SOLD_COINS_PROCESSED": "1", + "OPENFUT_FIFA17_SOLD_EXPERIMENT": "highest" + }, + "surfaces": { + "counts": { + "body": { + "count": 0, + "maxAuctionsAllowed": 100, + "offered": 0, + "selling": 0, + "sold": 1 + }, + "sha256": "766b1fcc4ca47442e6f570c81583840b6ae8df9cbeae52473b3c100ca881e206", + "status": 200 + }, + "tradePile": { + "body": { + "auctionInfo": [ + { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 1, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143, + "duplicateItemIdList": [], + "total": 1 + }, + "sha256": "6e228a34b5d7fac6bf70710cbd4ca35b58740e04ae455ffeee04b6ce7faab8e5", + "status": 200 + }, + "trade_status_filtered": { + "body": { + "auctionInfo": [ + { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 1, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143 + }, + "sha256": "c1557013bf20285d0512ca0d055a16df8036260559d640da11102a2379ac5986", + "status": 200 + }, + "trade_status_unfiltered": { + "body": { + "auctionInfo": [ + { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 1, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 212188, + "id": 100000178, + "itemState": "forSale", + "preferredPosition": "ST", + "rating": 75, + "resourceId": 212188, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143 + }, + "sha256": "c1557013bf20285d0512ca0d055a16df8036260559d640da11102a2379ac5986", + "status": 200 + } + } + } + } +} \ No newline at end of file diff --git a/docs/evidence/sold-ab-2026-08-18/phase0-variantA-staging-sold-row.json b/docs/evidence/sold-ab-2026-08-18/phase0-variantA-staging-sold-row.json new file mode 100644 index 0000000..0d86374 --- /dev/null +++ b/docs/evidence/sold-ab-2026-08-18/phase0-variantA-staging-sold-row.json @@ -0,0 +1,850 @@ +{ + "captured": "2026-08-18T02:21:49Z", + "phase": "0 \u2014 staging validated, sold row present, awaiting live client", + "settlement": { + "clubs": { + "club-buyer-b": 19850, + "club-seller-a-cage": 1143 + }, + "disposable_owner": [ + "club-buyer-b" + ], + "fee": 7, + "gross": 150, + "instances": 1, + "proceeds": 143 + }, + "sold_row": { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 232273, + "attributeList": [ + { + "index": 0, + "value": 62 + }, + { + "index": 1, + "value": 28 + }, + { + "index": 2, + "value": 33 + }, + { + "index": 3, + "value": 49 + }, + { + "index": 4, + "value": 53 + }, + { + "index": 5, + "value": 50 + } + ], + "cardassetid": 232273, + "cardsubtypeid": 0, + "contract": 7, + "definitionId": 232273, + "discardValue": 150, + "fitness": 99, + "id": 100000012, + "itemState": "forSale", + "itemType": "player", + "leagueId": 32, + "nation": 117, + "owners": 1, + "playStyle": 250, + "preferredPosition": "LB", + "rareflag": 1, + "rating": 51, + "resourceId": 232273, + "teamid": 112487, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + }, + "surfaces": { + "club": { + "body": { + "itemData": [ + { + "assetId": 20801, + "attributeList": [ + { + "index": 0, + "value": 97 + }, + { + "index": 1, + "value": 97 + }, + { + "index": 2, + "value": 86 + }, + { + "index": 3, + "value": 96 + }, + { + "index": 4, + "value": 38 + }, + { + "index": 5, + "value": 85 + } + ], + "cardassetid": 20801, + "cardsubtypeid": 0, + "contract": 7, + "definitionId": 83906881, + "discardValue": 1500, + "fitness": 99, + "id": 100000001, + "itemState": "free", + "itemType": "player", + "leagueId": 53, + "nation": 38, + "owners": 1, + "playStyle": 250, + "preferredPosition": "LW", + "rareflag": 11, + "rating": 99, + "resourceId": 83906881, + "teamid": 243, + "untradeable": false + }, + { + "assetId": 158023, + "attributeList": [ + { + "index": 0, + "value": 94 + }, + { + "index": 1, + "value": 95 + }, + { + "index": 2, + "value": 91 + }, + { + "index": 3, + "value": 99 + }, + { + "index": 4, + "value": 31 + }, + { + "index": 5, + "value": 66 + } + ], + "cardassetid": 158023, + "cardsubtypeid": 0, + "contract": 7, + "definitionId": 84044103, + "discardValue": 1500, + "fitness": 99, + "id": 100000002, + "itemState": "free", + "itemType": "player", + "leagueId": 53, + "nation": 52, + "owners": 1, + "playStyle": 250, + "preferredPosition": "RW", + "rareflag": 11, + "rating": 98, + "resourceId": 84044103, + "teamid": 241, + "untradeable": false + }, + { + "assetId": 167495, + "attributeList": [ + { + "index": 0, + "value": 94 + }, + { + "index": 1, + "value": 95 + }, + { + "index": 2, + "value": 99 + }, + { + "index": 3, + "value": 94 + }, + { + "index": 4, + "value": 64 + }, + { + "index": 5, + "value": 96 + } + ], + "cardassetid": 167495, + "cardsubtypeid": 0, + "contract": 7, + "definitionId": 84053575, + "discardValue": 1500, + "fitness": 99, + "id": 100000003, + "itemState": "free", + "itemType": "player", + "leagueId": 19, + "nation": 21, + "owners": 1, + "playStyle": 250, + "preferredPosition": "GK", + "rareflag": 11, + "rating": 97, + "resourceId": 84053575, + "teamid": 21, + "untradeable": false + }, + { + "assetId": 176580, + "attributeList": [ + { + "index": 0, + "value": 85 + }, + { + "index": 1, + "value": 93 + }, + { + "index": 2, + "value": 82 + }, + { + "index": 3, + "value": 90 + }, + { + "index": 4, + "value": 45 + }, + { + "index": 5, + "value": 82 + } + ], + "cardassetid": 176580, + "cardsubtypeid": 0, + "contract": 7, + "definitionId": 117617092, + "discardValue": 1500, + "fitness": 99, + "id": 100000004, + "itemState": "free", + "itemType": "player", + "leagueId": 53, + "nation": 60, + "owners": 1, + "playStyle": 250, + "preferredPosition": "ST", + "rareflag": 22, + "rating": 95, + "resourceId": 117617092, + "teamid": 241, + "untradeable": false + }, + { + "assetId": 177003, + "attributeList": [ + { + "index": 0, + "value": 78 + }, + { + "index": 1, + "value": 79 + }, + { + "index": 2, + "value": 90 + }, + { + "index": 3, + "value": 93 + }, + { + "index": 4, + "value": 76 + }, + { + "index": 5, + "value": 70 + } + ], + "cardassetid": 177003, + "cardsubtypeid": 0, + "contract": 7, + "definitionId": 151171947, + "discardValue": 1500, + "fitness": 99, + "id": 100000005, + "itemState": "free", + "itemType": "player", + "leagueId": 53, + "nation": 10, + "owners": 1, + "playStyle": 250, + "preferredPosition": "CM", + "rareflag": 24, + "rating": 93, + "resourceId": 151171947, + "teamid": 243, + "untradeable": false + }, + { + "assetId": 164240, + "attributeList": [ + { + "index": 0, + "value": 77 + }, + { + "index": 1, + "value": 60 + }, + { + "index": 2, + "value": 76 + }, + { + "index": 3, + "value": 76 + }, + { + "index": 4, + "value": 93 + }, + { + "index": 5, + "value": 82 + } + ], + "cardassetid": 164240, + "cardsubtypeid": 0, + "contract": 7, + "definitionId": 134381968, + "discardValue": 1500, + "fitness": 99, + "id": 100000006, + "itemState": "free", + "itemType": "player", + "leagueId": 16, + "nation": 54, + "owners": 1, + "playStyle": 250, + "preferredPosition": "CB", + "rareflag": 23, + "rating": 92, + "resourceId": 134381968, + "teamid": 73, + "untradeable": false + }, + { + "assetId": 182493, + "attributeList": [ + { + "index": 0, + "value": 69 + }, + { + "index": 1, + "value": 52 + }, + { + "index": 2, + "value": 69 + }, + { + "index": 3, + "value": 67 + }, + { + "index": 4, + "value": 92 + }, + { + "index": 5, + "value": 83 + } + ], + "cardassetid": 182493, + "cardsubtypeid": 0, + "contract": 7, + "definitionId": 151177437, + "discardValue": 1500, + "fitness": 99, + "id": 100000007, + "itemState": "free", + "itemType": "player", + "leagueId": 53, + "nation": 60, + "owners": 1, + "playStyle": 250, + "preferredPosition": "CB", + "rareflag": 24, + "rating": 92, + "resourceId": 151177437, + "teamid": 240, + "untradeable": false + }, + { + "assetId": 168651, + "attributeList": [ + { + "index": 0, + "value": 71 + }, + { + "index": 1, + "value": 89 + }, + { + "index": 2, + "value": 91 + }, + { + "index": 3, + "value": 87 + }, + { + "index": 4, + "value": 64 + }, + { + "index": 5, + "value": 71 + } + ], + "cardassetid": 168651, + "cardsubtypeid": 0, + "contract": 7, + "definitionId": 84054731, + "discardValue": 1500, + "fitness": 99, + "id": 100000008, + "itemState": "free", + "itemType": "player", + "leagueId": 53, + "nation": 10, + "owners": 1, + "playStyle": 250, + "preferredPosition": "CM", + "rareflag": 11, + "rating": 92, + "resourceId": 84054731, + "teamid": 241, + "untradeable": false + }, + { + "assetId": 182521, + "attributeList": [ + { + "index": 0, + "value": 48 + }, + { + "index": 1, + "value": 83 + }, + { + "index": 2, + "value": 91 + }, + { + "index": 3, + "value": 82 + }, + { + "index": 4, + "value": 72 + }, + { + "index": 5, + "value": 73 + } + ], + "cardassetid": 182521, + "cardsubtypeid": 0, + "contract": 7, + "definitionId": 134400249, + "discardValue": 1500, + "fitness": 99, + "id": 100000009, + "itemState": "free", + "itemType": "player", + "leagueId": 53, + "nation": 21, + "owners": 1, + "playStyle": 250, + "preferredPosition": "CM", + "rareflag": 23, + "rating": 91, + "resourceId": 134400249, + "teamid": 243, + "untradeable": false + }, + { + "assetId": 197445, + "attributeList": [ + { + "index": 0, + "value": 90 + }, + { + "index": 1, + "value": 77 + }, + { + "index": 2, + "value": 85 + }, + { + "index": 3, + "value": 87 + }, + { + "index": 4, + "value": 87 + }, + { + "index": 5, + "value": 77 + } + ], + "cardassetid": 197445, + "cardsubtypeid": 0, + "contract": 7, + "definitionId": 151192389, + "discardValue": 1500, + "fitness": 99, + "id": 100000010, + "itemState": "free", + "itemType": "player", + "leagueId": 19, + "nation": 4, + "owners": 1, + "playStyle": 250, + "preferredPosition": "LB", + "rareflag": 24, + "rating": 91, + "resourceId": 151192389, + "teamid": 21, + "untradeable": false + }, + { + "assetId": 121939, + "attributeList": [ + { + "index": 0, + "value": 70 + }, + { + "index": 1, + "value": 58 + }, + { + "index": 2, + "value": 84 + }, + { + "index": 3, + "value": 86 + }, + { + "index": 4, + "value": 88 + }, + { + "index": 5, + "value": 66 + } + ], + "cardassetid": 121939, + "cardsubtypeid": 0, + "contract": 7, + "definitionId": 100785235, + "discardValue": 1500, + "fitness": 99, + "id": 100000011, + "itemState": "free", + "itemType": "player", + "leagueId": 19, + "nation": 21, + "owners": 1, + "playStyle": 250, + "preferredPosition": "RB", + "rareflag": 21, + "rating": 90, + "resourceId": 100785235, + "teamid": 21, + "untradeable": false + } + ] + }, + "sha256": "0a916e8e129a9c05294784738c305d651beedd58ebd914a6abfa94bd0a59abc7", + "status": 200 + }, + "counts": { + "body": { + "count": 0, + "maxAuctionsAllowed": 100, + "offered": 0, + "selling": 0, + "sold": 1 + }, + "sha256": "766b1fcc4ca47442e6f570c81583840b6ae8df9cbeae52473b3c100ca881e206", + "status": 200 + }, + "tradePile": { + "body": { + "auctionInfo": [ + { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 232273, + "attributeList": [ + { + "index": 0, + "value": 62 + }, + { + "index": 1, + "value": 28 + }, + { + "index": 2, + "value": 33 + }, + { + "index": 3, + "value": 49 + }, + { + "index": 4, + "value": 53 + }, + { + "index": 5, + "value": 50 + } + ], + "cardassetid": 232273, + "cardsubtypeid": 0, + "contract": 7, + "definitionId": 232273, + "discardValue": 150, + "fitness": 99, + "id": 100000012, + "itemState": "forSale", + "itemType": "player", + "leagueId": 32, + "nation": 117, + "owners": 1, + "playStyle": 250, + "preferredPosition": "LB", + "rareflag": 1, + "rating": 51, + "resourceId": 232273, + "teamid": 112487, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143, + "duplicateItemIdList": [], + "total": 1 + }, + "sha256": "c792ebcf179a5c617ffe02859ad39eba830a4d5b5224a0953ecef774ee4cf75b", + "status": 200 + }, + "trade_status_filtered": { + "body": { + "auctionInfo": [ + { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 232273, + "attributeList": [ + { + "index": 0, + "value": 62 + }, + { + "index": 1, + "value": 28 + }, + { + "index": 2, + "value": 33 + }, + { + "index": 3, + "value": 49 + }, + { + "index": 4, + "value": 53 + }, + { + "index": 5, + "value": 50 + } + ], + "cardassetid": 232273, + "cardsubtypeid": 0, + "contract": 7, + "definitionId": 232273, + "discardValue": 150, + "fitness": 99, + "id": 100000012, + "itemState": "forSale", + "itemType": "player", + "leagueId": 32, + "nation": 117, + "owners": 1, + "playStyle": 250, + "preferredPosition": "LB", + "rareflag": 1, + "rating": 51, + "resourceId": 232273, + "teamid": 112487, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143 + }, + "sha256": "db79d9fcfc47ae680b44311598e82918b8afd3284160143c33e525adf693f30e", + "status": 200 + }, + "trade_status_unfiltered": { + "body": { + "auctionInfo": [ + { + "bidState": "highest", + "buyNowPrice": 150, + "coinsProcessed": 0, + "currentBid": 150, + "expires": 0, + "itemData": { + "assetId": 232273, + "attributeList": [ + { + "index": 0, + "value": 62 + }, + { + "index": 1, + "value": 28 + }, + { + "index": 2, + "value": 33 + }, + { + "index": 3, + "value": 49 + }, + { + "index": 4, + "value": 53 + }, + { + "index": 5, + "value": 50 + } + ], + "cardassetid": 232273, + "cardsubtypeid": 0, + "contract": 7, + "definitionId": 232273, + "discardValue": 150, + "fitness": 99, + "id": 100000012, + "itemState": "forSale", + "itemType": "player", + "leagueId": 32, + "nation": 117, + "owners": 1, + "playStyle": 250, + "preferredPosition": "LB", + "rareflag": 1, + "rating": 51, + "resourceId": 232273, + "teamid": 112487, + "untradeable": false + }, + "sellerEstablished": 1, + "sellerName": "CAGE", + "startingBid": 150, + "tradeId": 900500150, + "tradeState": "closed", + "watched": false + } + ], + "credits": 1143 + }, + "sha256": "db79d9fcfc47ae680b44311598e82918b8afd3284160143c33e525adf693f30e", + "status": 200 + } + }, + "topology": { + "advertises_utas": "http://:8299/", + "blaze": [ + 42327, + 42330, + 42331 + ], + "core": 18081, + "utas_host": 8299 + }, + "variant": "A_highest (bidState=highest, coinsProcessed=0, countMode=active)" +} \ No newline at end of file diff --git a/scripts/sold-ab-differential.py b/scripts/sold-ab-differential.py new file mode 100755 index 0000000..36a9f2a --- /dev/null +++ b/scripts/sold-ab-differential.py @@ -0,0 +1,375 @@ +#!/usr/bin/env python3 +"""PRE-LIVE machine differential for the seller-facing sold-row A/B. + +Purpose: prove, before any operator time is spent at a real FIFA client, that the +two variants differ in EXACTLY the field under test. A contaminated A/B cannot +attribute the client's reaction to anything, so this gate runs first. + +It performs ONE synthetic settlement, then re-reads every seller-facing surface +under each variant by restarting only the host (same Core, same DBs, same sale), and +diffs the payloads with explicit classification: + + MISSING key present in A, absent in B + EXTRA key absent in A, present in B + TYPE_MISMATCH same key, different JSON type + VALUE_MISMATCH same key and type, different value + +Two orthogonal pairs are checked: + + PRIMARY bidState highest vs buyNow coinsProcessed held at 0 + ORTHOGONAL coinsProcessed 0 vs 1 bidState held at highest + +Plus the two counts states the live experiment needs to interpret: + + S1 0 active + 1 sold + S2 1 active + 1 sold + +Evidence is written per variant under docs/evidence/sold-ab-/ with a sha256 +of each payload, so variant A evidence can never be confused with variant B. + +ISOLATION: reuses the verified staging bring-up from sold-wire-check.py, which binds +only ephemeral loopback ports, refuses every production port, and opens nothing under +/home/alex/openfut-promotion/state/. + + python3 scripts/sold-ab-differential.py [--out DIR] [--keep] +""" +import argparse +import hashlib +import importlib.util +import json +import os +import shutil +import sqlite3 +import subprocess +import sys +import tempfile +import time + +REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +# Reuse the proven harness helpers rather than re-deriving them. The filename has +# hyphens, so it cannot be imported normally. +_spec = importlib.util.spec_from_file_location( + "sold_wire_check", os.path.join(REPO, "scripts", "sold-wire-check.py")) +H = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(H) + +SURFACES = { + "tradePile": "/ut/game/fifa17/tradePile", + "counts": "/ut/game/fifa17/tradePile/counts", + "trade_status_filtered": f"/ut/game/fifa17/trade/status?tradeIds={H.TRADE_ID}", + "trade_status_unfiltered": "/ut/game/fifa17/trade/status", +} + +results = [] + + +def check(label, ok, detail=""): + results.append((label, bool(ok), detail)) + print(f" [{'PASS' if ok else 'FAIL'}] {label}{(': ' + detail) if detail else ''}") + return ok + + +def banner(t): + print("\n" + "=" * 74) + print(f"== {t}") + print("=" * 74) + + +def jtype(v): + if isinstance(v, bool): + return "bool" + if isinstance(v, int): + return "int" + if isinstance(v, float): + return "float" + if isinstance(v, str): + return "str" + if isinstance(v, list): + return "list" + if isinstance(v, dict): + return "dict" + return "null" + + +def classify(a, b, path=""): + """Recursive classified diff. Returns a list of (kind, path, a, b).""" + out = [] + if jtype(a) != jtype(b): + return [("TYPE_MISMATCH", path or "", jtype(a), jtype(b))] + if isinstance(a, dict): + for k in a: + if k not in b: + out.append(("MISSING", f"{path}.{k}".lstrip("."), a[k], None)) + for k in b: + if k not in a: + out.append(("EXTRA", f"{path}.{k}".lstrip("."), None, b[k])) + for k in a: + if k in b: + out += classify(a[k], b[k], f"{path}.{k}".lstrip(".")) + return out + if isinstance(a, list): + if len(a) != len(b): + out.append(("VALUE_MISMATCH", f"{path}[len]", len(a), len(b))) + for i, (x, y) in enumerate(zip(a, b)): + out += classify(x, y, f"{path}[{i}]") + return out + if a != b: + out.append(("VALUE_MISMATCH", path or "", a, b)) + return out + + +def capture(port): + """Every seller-facing surface, as parsed JSON plus a sha256 of the raw bytes.""" + snap = {} + for name, path in SURFACES.items(): + st, body = H.req(port, "GET", path) + raw = json.dumps(body, sort_keys=True, separators=(",", ":")).encode() + snap[name] = { + "status": st, + "body": body, + "sha256": hashlib.sha256(raw).hexdigest(), + } + return snap + + +def sold_row(snap): + rows = snap["tradePile"]["body"].get("auctionInfo", []) + return rows[0] if rows else {} + + +def report_diff(label, a, b, allowed): + """Print the classified diff and assert only `allowed` paths differ.""" + diffs = classify(a, b) + print(f" classified diff ({len(diffs)} finding(s)):") + for kind, path, av, bv in diffs: + print(f" {kind:15s} {path:34s} A={av!r} B={bv!r}") + kinds = {k for k, _, _, _ in diffs} + for bad in ("MISSING", "EXTRA", "TYPE_MISMATCH"): + check(f"{label}: no {bad}", bad not in kinds, + ", ".join(p for k, p, _, _ in diffs if k == bad) or "none") + paths = sorted(p for _, p, _, _ in diffs) + check(f"{label}: only {allowed} differ", paths == sorted(allowed), str(paths)) + return diffs + + +def restart_host(state, variant, coins_processed): + """Same Core, same DBs, same settled sale — only the host's variant changes.""" + if state.get("host"): + state["host"].terminate() + state["host"].wait(timeout=20) + state["host"] = H.start_host(state["tmp"], state["host_port"], state["core_port"], + state["host_log"], variant, + coins_processed=coins_processed) + return state["host"] + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--out", default=os.path.join( + REPO, "docs/evidence", f"sold-ab-{time.strftime('%Y-%m-%d')}")) + ap.add_argument("--keep", action="store_true") + args = ap.parse_args() + os.makedirs(args.out, exist_ok=True) + + tmp = tempfile.mkdtemp(prefix="openfut-sold-ab-") + state = {"tmp": tmp, "host": None} + evidence = {"generated": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), + "variants": {}, "diffs": {}, "counts_states": {}} + try: + state["core_port"] = H.free_port() + state["host_port"] = H.free_port() + state["core_log"] = os.path.join(tmp, "core.log") + state["host_log"] = os.path.join(tmp, "host.log") + banner("ISOLATED STAGING") + print(f" tmp {tmp}\n core 127.0.0.1:{state['core_port']}" + f"\n host 127.0.0.1:{state['host_port']}") + core, core_db = H.start_core(tmp, state["core_port"], state["core_log"]) + state["core"] = core + state["core_db"] = core_db + mdb = os.path.join(tmp, "market.db") + + # ---- one authentic listing + one real settlement ------------------- + restart_host(state, "highest", "0") + con = sqlite3.connect(mdb) + con.execute( + "INSERT INTO listings (listing_id, card_id, core_item_id, wire_item_id, " + "wire_resource_id, start_price, buy_now_price, owner, state, created_at, " + "item_json, duration_secs) VALUES (?,?,?,?,?,?,?,?, 'active', ?, ?, ?)", + (H.TRADE_ID, H.CARD, H.ITEM, 100000178, 212188, H.GROSS, H.GROSS, "CAGE", + str(int(time.time() * 1000)), json.dumps({ + "id": 100000178, "resourceId": 212188, "rating": 75, + "preferredPosition": "ST", "itemState": "forSale", + "untradeable": False, "assetId": 212188}), 3600)) + con.commit(); con.close() + banner(f"SETTLE — synthetic Buyer B buys at {H.GROSS}") + out = subprocess.run( + [os.path.join(REPO, "target/release/staging_sell"), + "--market-db", mdb, "--core-url", f"http://127.0.0.1:{state['core_port']}", + "--trade-id", H.TRADE_ID, "--item", H.ITEM, + "--seller", H.SELLER_CLUB, "--buyer", H.BUYER_CLUB, + "--gross", str(H.GROSS)], + capture_output=True, text=True, timeout=120) + print(" " + "\n ".join((out.stdout + out.stderr).strip().splitlines())) + check("settlement succeeded", out.returncode == 0) + own, n, coins = H.owner_of(core_db, H.ITEM) + fee = H.GROSS * 5 // 100 + check("ownership -> buyer", own == H.BUYER_CLUB, str(own)) + check("exactly one authoritative instance", n == 1, str(n)) + check("seller credited net", coins[H.SELLER_CLUB] == 1000 + H.GROSS - fee, + str(coins[H.SELLER_CLUB])) + check("buyer debited gross", coins[H.BUYER_CLUB] == 20000 - H.GROSS, + str(coins[H.BUYER_CLUB])) + economy = {"seller": coins[H.SELLER_CLUB], "buyer": coins[H.BUYER_CLUB], + "fee": fee, "owner": own, "instances": n} + evidence["settlement"] = economy + + # ---- PRIMARY A/B: bidState only ------------------------------------ + banner("PRIMARY A/B — bidState highest vs buyNow (coinsProcessed held at 0)") + snaps = {} + for name, variant in (("A_highest", "highest"), ("B_buyNow", "buyNow")): + restart_host(state, variant, "0") + snaps[name] = capture(state["host_port"]) + evidence["variants"][name] = { + "env": {"OPENFUT_FIFA17_SOLD_EXPERIMENT": variant, + "OPENFUT_FIFA17_SOLD_COINS_PROCESSED": "0"}, + "surfaces": snaps[name], + } + row = sold_row(snaps[name]) + print(f" {name}: tradeState={row.get('tradeState')} " + f"bidState={row.get('bidState')} currentBid={row.get('currentBid')} " + f"coinsProcessed={row.get('coinsProcessed')} atoms={len(row)}") + # Every variant must independently be a well-formed sold row. + check(f"{name}: tradeState closed", row.get("tradeState") == "closed") + check(f"{name}: twelve atoms", len(row) == 12, str(len(row))) + check(f"{name}: currentBid == gross", row.get("currentBid") == H.GROSS) + check(f"{name}: expires 0", row.get("expires") == 0) + check(f"{name}: counts.sold == 1", + snaps[name]["counts"]["body"].get("sold") == 1) + evidence["diffs"]["primary_bidstate"] = [ + {"kind": k, "path": p, "a": av, "b": bv} + for k, p, av, bv in report_diff( + "PRIMARY", snaps["A_highest"]["tradePile"]["body"], + snaps["B_buyNow"]["tradePile"]["body"], + ["auctionInfo[0].bidState"]) + ] + # /trade/status must move in lockstep, or the screen would contradict itself. + report_diff("PRIMARY /trade/status", + snaps["A_highest"]["trade_status_filtered"]["body"], + snaps["B_buyNow"]["trade_status_filtered"]["body"], + ["auctionInfo[0].bidState"]) + report_diff("PRIMARY counts", + snaps["A_highest"]["counts"]["body"], + snaps["B_buyNow"]["counts"]["body"], []) + + # ---- ORTHOGONAL A/B: coinsProcessed only --------------------------- + banner("ORTHOGONAL A/B — coinsProcessed 0 vs 1 (bidState held at highest)") + for name, cp in (("C_cp0", "0"), ("D_cp1", "1")): + restart_host(state, "highest", cp) + snaps[name] = capture(state["host_port"]) + evidence["variants"][name] = { + "env": {"OPENFUT_FIFA17_SOLD_EXPERIMENT": "highest", + "OPENFUT_FIFA17_SOLD_COINS_PROCESSED": cp}, + "surfaces": snaps[name], + } + row = sold_row(snaps[name]) + print(f" {name}: bidState={row.get('bidState')} " + f"coinsProcessed={row.get('coinsProcessed')}") + evidence["diffs"]["orthogonal_coinsprocessed"] = [ + {"kind": k, "path": p, "a": av, "b": bv} + for k, p, av, bv in report_diff( + "ORTHOGONAL", snaps["C_cp0"]["tradePile"]["body"], + snaps["D_cp1"]["tradePile"]["body"], + ["auctionInfo[0].coinsProcessed"]) + ] + check("C_cp0 is identical to A_highest (same env => same bytes)", + snaps["C_cp0"]["tradePile"]["sha256"] + == snaps["A_highest"]["tradePile"]["sha256"], + "reproducible") + + # ---- counts states the live run must interpret ---------------------- + banner("COUNTS STATES") + restart_host(state, "highest", "0") + s1 = H.req(state["host_port"], "GET", SURFACES["counts"])[1] + print(f" S1 0 active + 1 sold -> {json.dumps(s1)}") + con = sqlite3.connect(mdb) + con.execute( + "INSERT INTO listings (listing_id, card_id, core_item_id, wire_item_id, " + "wire_resource_id, start_price, buy_now_price, owner, state, created_at, " + "item_json, duration_secs) VALUES (?,?,?,?,?,?,?,?, 'active', ?, ?, ?)", + ("900500999", H.CARD, "core-second-x", 100000179, 212188, 300, 400, "CAGE", + str(int(time.time() * 1000)), json.dumps({ + "id": 100000179, "resourceId": 212188, "rating": 75, + "preferredPosition": "ST", "itemState": "forSale", + "untradeable": False, "assetId": 212188}), 3600)) + con.commit(); con.close() + s2_active = H.req(state["host_port"], "GET", SURFACES["counts"])[1] + print(f" S2 1 active + 1 sold -> {json.dumps(s2_active)} (count mode: active)") + restart_host(state, "highest", "0") + os.environ["_"] = "_" # no-op; count mode is a host env, set below + state["host"].terminate(); state["host"].wait(timeout=20) + state["host"] = H.start_host(tmp, state["host_port"], state["core_port"], + state["host_log"], "highest", + coins_processed="0", + count_mode="active_plus_sold") + s2_member = H.req(state["host_port"], "GET", SURFACES["counts"])[1] + print(f" S2 1 active + 1 sold -> {json.dumps(s2_member)} (count mode: membership)") + evidence["counts_states"] = { + "S1_zero_active_one_sold": s1, + "S2_one_active_one_sold_count_active": s2_active, + "S2_one_active_one_sold_count_membership": s2_member, + } + check("S1 reports sold 1 / selling 0", s1.get("sold") == 1 and s1.get("selling") == 0) + check("S2 reports sold 1 / selling 1", + s2_active.get("sold") == 1 and s2_active.get("selling") == 1) + check("S2 count differs by mode (1 vs 2) — the open question for the client", + s2_active.get("count") == 1 and s2_member.get("count") == 2, + f"{s2_active.get('count')} vs {s2_member.get('count')}") + + # ---- emit evidence -------------------------------------------------- + banner("EVIDENCE") + for name, v in evidence["variants"].items(): + path = os.path.join(args.out, f"{name}.json") + with open(path, "w") as f: + json.dump(v, f, indent=2, sort_keys=True) + print(f" {os.path.relpath(path, REPO)} " + f"tradePile sha256={v['surfaces']['tradePile']['sha256'][:16]}…") + idx = os.path.join(args.out, "differential.json") + with open(idx, "w") as f: + json.dump(evidence, f, indent=2, sort_keys=True) + print(f" {os.path.relpath(idx, REPO)}") + + banner("PRODUCTION UNTOUCHED") + alive = subprocess.run(["ps", "-o", "pid=", "-p", "3631953"], + capture_output=True, text=True).stdout.strip() + check("prod-host pid 3631953 alive", alive == "3631953", alive or "gone") + + banner("RESULT") + passed = sum(1 for _, ok, _ in results if ok) + failed = [l for l, ok, _ in results if not ok] + print(f" {passed}/{len(results)} checks passed") + if failed: + print(" FAILED: " + "; ".join(failed)) + print("\n A/B IS CONTAMINATED — do NOT run the live experiment") + return 1 + print("\n A/B IS CLEAN — one-field attribution proven; ready for the live client") + return 0 + finally: + for k in ("host", "core"): + p = state.get(k) + if p: + try: + p.terminate(); p.wait(timeout=15) + except Exception: + try: + p.kill() + except Exception: + pass + if args.keep: + print(f"\n kept {tmp}") + else: + shutil.rmtree(tmp, ignore_errors=True) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/sold-client-ports.py b/scripts/sold-client-ports.py new file mode 100755 index 0000000..bd077c2 --- /dev/null +++ b/scripts/sold-client-ports.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +"""Switch the FIFA client's Blaze ports between PRODUCTION and STAGING, reversibly. + +The staging sold experiment needs the client to talk to the staging Blaze (which +advertises the staging UTAS base). The ONLY client change required is the two Blaze +port lines in `openfut.cfg`; `host` and `https_port` are left alone. + +Safety properties, in order of importance: + + * The production values are recorded to a sidecar file on the client BEFORE the + first edit, and `restore` reads that sidecar rather than assuming what + production was. If the sidecar is missing, restore refuses. + * `restore` is idempotent and safe to run at any time, including after a crash. + * Every operation re-reads the file afterwards and prints it, so the result is + verified rather than assumed. + * It refuses to edit while a FIFA client is running: the hook reads this file at + connect time, so changing it under a live session is unsafe. + * Only the two known keys are rewritten. Unknown lines are passed through + untouched, and a missing key is an error rather than a silent append. + + python3 scripts/sold-client-ports.py show + python3 scripts/sold-client-ports.py staging # 42327 / 42330 + python3 scripts/sold-client-ports.py restore # back to the recorded values +""" +import subprocess +import sys + +CLIENT = "alex@10.10.0.105" +CFG = "/mnt/games/FIFA 17/openfut.cfg" +SIDECAR = "/mnt/games/FIFA 17/openfut.cfg.openfut-prod-ports" +KEYS = ("blaze_redirector_port", "blaze_main_port") +STAGING = {"blaze_redirector_port": "42327", "blaze_main_port": "42330"} + + +def ssh(script): + r = subprocess.run(["ssh", "-o", "BatchMode=yes", CLIENT, script], + capture_output=True, text=True, timeout=60) + if r.returncode != 0: + raise SystemExit(f"ssh failed ({r.returncode}): {r.stderr.strip()}") + return r.stdout + + +def read_cfg(): + return ssh(f'cat "{CFG}"') + + +def parse(text): + out = {} + for line in text.splitlines(): + if "=" in line and not line.strip().startswith("#"): + k, _, v = line.partition("=") + out[k.strip()] = v.strip() + return out + + +def fifa_running(): + return bool(ssh("pgrep -f FIFA17.exe || true").strip()) + + +def show(): + text = read_cfg() + print(f"--- {CFG}") + print(text.rstrip()) + cur = parse(text) + print("--- blaze ports:", {k: cur.get(k) for k in KEYS}) + side = ssh(f'cat "{SIDECAR}" 2>/dev/null || true').strip() + print("--- recorded production ports:", side or "(none recorded yet)") + print("--- FIFA client running:", "YES" if fifa_running() else "no") + return cur + + +def write_ports(values, label): + if fifa_running(): + raise SystemExit( + "REFUSING to edit openfut.cfg while a FIFA client is running.\n" + "The hook reads this file at connect time; exit FIFA first." + ) + text = read_cfg() + cur = parse(text) + for k in KEYS: + if k not in cur: + raise SystemExit(f"key {k!r} is absent from {CFG}; refusing to guess it") + # Record production values once, before the first mutation, so restore never + # has to assume what they were. + existing_sidecar = ssh(f'cat "{SIDECAR}" 2>/dev/null || true').strip() + if not existing_sidecar and label == "staging": + rec = "\n".join(f"{k}={cur[k]}" for k in KEYS) + ssh(f'cat > "{SIDECAR}" <<\'EOF\'\n{rec}\nEOF') + print(f"recorded production ports to {SIDECAR}:\n{rec}") + + out = [] + for line in text.splitlines(): + k = line.partition("=")[0].strip() + out.append(f"{k}={values[k]}" if k in values else line) + body = "\n".join(out) + "\n" + ssh(f'cat > "{CFG}" <<\'EOF\'\n{body.rstrip()}\nEOF') + after = parse(read_cfg()) + for k, v in values.items(): + if after.get(k) != v: + raise SystemExit(f"VERIFY FAILED: {k} is {after.get(k)!r}, expected {v!r}") + print(f"--- now pointing at {label}") + print(read_cfg().rstrip()) + + +def main(): + if len(sys.argv) != 2 or sys.argv[1] not in ("show", "staging", "restore"): + print(__doc__) + return 2 + cmd = sys.argv[1] + if cmd == "show": + show() + return 0 + if cmd == "staging": + write_ports(STAGING, "staging") + return 0 + side = ssh(f'cat "{SIDECAR}" 2>/dev/null || true').strip() + if not side: + raise SystemExit( + f"no recorded production ports at {SIDECAR}; refusing to guess.\n" + "Set them by hand and verify with `show`." + ) + write_ports(parse(side), "production (restored)") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/sold-wire-check.py b/scripts/sold-wire-check.py index 45fe67b..fc5e5f2 100755 --- a/scripts/sold-wire-check.py +++ b/scripts/sold-wire-check.py @@ -299,8 +299,11 @@ def main(): banner("VARIANT B — same state, restart host with closed / buyNow") host.terminate(); host.wait(timeout=20); procs.remove(host) + # coinsProcessed is held CONSTANT at 0 here: the primary A/B must be + # attributable to bidState alone. The orthogonal coinsProcessed test is a + # separate variant pair (see scripts/sold-ab-differential.py). host = start_host(tmp, host_port, core_port, host_log, "buyNow", - coins_processed="1", count_mode="active_plus_sold") + coins_processed="0", count_mode="active_plus_sold") procs.append(host) st, pileb = req(host_port, "GET", "/ut/game/fifa17/tradePile") st2, countsb = req(host_port, "GET", "/ut/game/fifa17/tradePile/counts") @@ -308,13 +311,13 @@ def main(): print(f" bidState={b_row['bidState']} coinsProcessed={b_row['coinsProcessed']} " f"counts={json.dumps(countsb)}") check("bidState buyNow (variant B)", b_row.get("bidState") == "buyNow") - check("coinsProcessed 1 when asked", b_row.get("coinsProcessed") == 1) + check("coinsProcessed held constant at 0", b_row.get("coinsProcessed") == 0) check("count_mode active_plus_sold counts the sold row", countsb.get("count") == 1 and countsb.get("sold") == 1, json.dumps(countsb)) differing = sorted(k for k in a_row if a_row.get(k) != b_row.get(k)) - check("A/B differ ONLY in bidState and coinsProcessed", - differing == ["bidState", "coinsProcessed"], str(differing)) + check("A/B differ ONLY in bidState (one-field attribution)", + differing == ["bidState"], str(differing)) banner("CLEAR — the PE-proven bulk verb DELETE .../trade/sold") pre_coins = owner_of(core_db, ITEM)[2]