test(market): make the sold A/B one-field attributable, add classified differential
The brief's gate: if the harness varies bidState AND coinsProcessed together, the client's reaction is attributable to neither. The env knobs were already orthogonal (--variant and --coins-processed are independent, cp defaults to 0), but sold-wire-check.py was flipping BOTH for variant B as a convenience, which is exactly the contaminated A/B the brief forbids. Fixed: the primary pair now holds coinsProcessed at 0 and asserts the differing-field set is exactly ['bidState']. New scripts/sold-ab-differential.py is the pre-live gate. It settles ONE synthetic sale, then re-reads every seller-facing surface under each variant by restarting only the host (same Core, same DBs, same sale), and diffs with explicit classification -- MISSING / EXTRA / TYPE_MISMATCH / VALUE_MISMATCH -- rather than a boolean "equal?". Two orthogonal pairs: PRIMARY bidState highest vs buyNow, coinsProcessed held at 0 ORTHOGONAL coinsProcessed 0 vs 1, bidState held at highest Result, 36/36: the ONLY finding on /tradePile is VALUE_MISMATCH auctionInfo[0].bidState A='highest' B='buyNow'; /trade/status differs in exactly the same one path; counts are byte-identical. The orthogonal pair's only finding is auctionInfo[0].coinsProcessed. C_cp0's sha256 equals A_highest's, so the capture is reproducible rather than merely consistent. Counts states the live run has to interpret, measured not guessed: S1 0 active + 1 sold -> count 0, selling 0, sold 1 S2 1 active + 1 sold -> count 1 (active mode) vs 2 (membership mode) That divergence IS the open question for the client; production is unchanged. scripts/sold-client-ports.py switches ONLY the two client Blaze port lines, and is built so restoration cannot depend on memory: it records the production values to a sidecar on the client BEFORE the first edit and restore reads that sidecar, refusing if it is absent. It rewrites only known keys (a missing key is an error, never a silent append), re-reads and verifies afterwards, and REFUSES to edit while a FIFA client is running because the hook reads the file at connect time. Phase 0 evidence under docs/evidence/sold-ab-2026-08-18/ with a sha256 per surface, one file per variant so A can never overwrite B. Live client A/B NOT run: a production FIFA session is currently live on 10.10.0.105 (pid 32188), and live-session mutual exclusion applies. The client config was NOT touched -- the switcher's guard refused, as designed. Production untouched: prod-host pid 3631953, coins 29,843,976, /tradePile 0, counts.sold 0, club 1966; nothing under /home/alex/openfut-promotion/state/ opened.
This commit is contained in:
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user