Commit Graph

4 Commits

Author SHA1 Message Date
funman300 f9740f640d feat(fifa17): route SBCs through atomic Rust Core 2026-08-18 18:26:35 +00:00
OpenFUT Agent b1643309f6 test(fifa17): prove host economy concurrency and failure rollback
Two real host-dispatch test files (no fakes) driving Server::try_handle_economy
against a live in-process Core over the real blocking client + durable
MarketStore/PileStore + JsonIdentityStore, each racer its own OS thread
(off-runtime pattern).

economy_concurrency.rs — 8 races x 50 iterations:
  A two BUYs (coins for one) -> exactly one 200 + one 461, final 0, one debit.
  B duplicate owned-pack open -> one redemption, +11 once, entitlement once.
  C duplicate quick-sell -> one sell + one credit + one removal.
  D two market buyers -> one win, one debit, one mint, sold once.
  E reward+BUY -> no lost update (Core relative UPDATE under BEGIN IMMEDIATE).
  F move+quick-sell / G list+quick-sell -> one coherent transition.
  H 1000 concurrent mints -> unique + reversible wire ids, monotonic watermark.

economy_failure.rs — 10 fault-injection sub-cases, all fail-closed:
  BUY/open-redeem/generator/pile/identity, quick-sell, move, market
  reserve/purchase/complete. CRITICAL complete-sale-after-commit = SAFE: the
  listing is left `reserved` (not active), so the active->reserved reserve CAS
  can never win again -> not buyable, exactly one debit + one mint. No E3.

Fault injection uses test-file CoreEconomy/ExternalIdentityStore doubles plus a
NARROW, inert-by-default `StoreFault` seam in market_store.rs + pile_store.rs
(the concrete stores have no trait boundary; 3 `tripped()` checks + a field,
zero behaviour unless a test arms it). `parking_lot` promoted to a normal dep
(the seam's Mutex is used at lib scope). Classifier/ROUTE_AUTHORITY/Python
untouched. host lib 71/71; both new tests pass.
2026-08-13 22:30:35 +00:00
OpenFUT Agent 747cc234c1 fix(fifa17): preserve owned-pack reveal state for GET /purchased
Closes the reveal contract gap: POST /purchased opens a pack and returns
metadata; the client then polls GET /purchased for the opened items. Store BUY
returns items inline, but owned reward-pack (e.g. pack 70) opens had no reveal
read path, so a real FIFA session would show nothing after opening.

Faithful to the Python oracle (fut_store.last_pack / purchased pile): the reveal
is the set of owned items currently in the FIFA "purchased" pile — durable,
idempotent on repeat GET, cleared per-item when a card is moved to the club, and
appended-to by each open. Not a replay cache; presentation state derived from
the durable pile store + Core inventory.

- pile_store.rs: `list_by_pile(pile) -> Vec<core_item_id>` (reveal membership).
- economy_store.rs: `PurchasedPileSink` trait + optional `StoreDeps.purchased`;
  handle_store_buy/handle_pack_open record each minted item into the "purchased"
  pile. `shape_purchased_reveal` (pure): filter Core inventory to the purchased
  pile, shape with the SAME `shape_club_response` /club uses. Grants nothing,
  consumes no entitlement, allocates no id, moves no coins.
- lib.rs: EconomyRoute::PackReveal + classify_economy (GET purchased);
  BridgedPurchasedSink (records via the runtime bridge from the sync dispatch
  thread); dispatch reads the pile async + Core inventory sync + pure-shapes.

Scoping: single fifa17 profile/club (like the Python oracle), so all sessions
share one purchased pile — DIFFERENT-BY-DESIGN vs a per-SID cache, matching the
oracle's single-profile model.

Tests: pile_store::list_by_pile_filters_and_reflects_moves; and the dispatch E2E
now opens pack 70 (entitlement seeded via the Core economy API) and asserts GET
/purchased reveals the opened items and is idempotent on repeat. host 71 lib +
2 integration + 24 host_test green; clippy -D warnings + fmt clean.
2026-08-13 21:51:12 +00:00
OpenFUT Agent 4d2b8b9be3 economy(fifa17): land Store + Market writer handlers + pack generator (unrouted)
Implements the FIFA17 economy WRITER cluster on top of the landed Core
economy authority + host CoreEconomy client + identity/item-shaper infra.
Handlers are pub, unit-tested, and NOT yet routed: classify() and
ROUTE_AUTHORITY are untouched — the classifier barrier is a later single
coherent flip. No stubs; real Core-backed behavior; fail-closed on CoreError.

Pack generator (adapter fut/pack_content.rs):
  generate_pack_contents(&PackDef, &mut impl Rng, &[GeneratedCandidate])
  -> Vec<GeneratedCard>. Pure, seeded (deterministic), gold-tier split +
  special_chance gate as documented OPENFUT PLACEHOLDER policy (Python
  open_pack/_pack_body parity note inline). Fail-closed empty on empty pool.

Store/item writers (host economy_store.rs), matching oracle wire shapes:
  - handle_store_buy   PUT /store/transaction -> purchase_items (debit+mint N)
    -> createPackResponse; cancel/unknown/owned_only -> 200 {}; insufficient
    -> 461 {reason,credits}; CoreError -> 503.
  - handle_pack_open   POST /purchased -> owned_only consumes the unopened
    entitlement (redeem_entitlement, consume-once); normal packs debit+mint.
  - handle_quick_sell{_path,_body}  DELETE .../item/<id> + POST /ut/delete/.../item
    -> reverse-resolve wire->Core id (SquadWireResolver) -> sell_item ->
    {items:[{id}],totalCredits}; not-owned skipped.
  Production OwnedItemLookup = CoreItemLookup over CoreAccess.

Market (host market_store.rs / pile_store.rs / market.rs), synthetic-seller:
  - MarketStore over sqlx SQLite (WAL-once + busy_timeout=5s + BEGIN IMMEDIATE
    for writes, mirroring openfut-core::db). listings(active/reserved/sold/
    cancelled), owner-checked cancel, CAS reserve/complete_sale/rollback.
    Typed errors NotFound/Sold/Cancelled/WrongOwner/Conflict.
  - PileStore: durable pile/location metadata keyed by Core item id.
  - handle_market_{list,query,cancel,buy} + handle_move_items. Buy-now =
    reserve (CAS) -> balance precheck (461) -> Core purchase_item (mint+debit)
    -> complete_sale; any Core failure rolls the reservation back active.
    Two concurrent buyers -> exactly one sale + one debit.

Deps (additive): rand 0.8 (adapter+host), sqlx 0.7 sqlite/runtime-tokio (host).
Tests: adapter +7 (pack_content), host +43 (economy_store 20, market/store 23
incl two_reservers_exactly_one_wins, two_buyers_exactly_one_sale_one_debit,
state_survives_reopen, move_persists_across_reopen). All green; clippy
-D warnings clean; rustfmt clean.
2026-08-13 20:47:57 +00:00