Core economy HTTP API + host CoreEconomy client (fail-closed) + credits reader landed; classifier not yet flipped (coherent barrier pending full cluster + Core seed).
6.8 KiB
FIFA17 UTAS Route Authority (economy cutover gate)
Machine-auditable ownership of every FIFA17 UTAS route that touches the economy
cluster. This is the deployment gate for the Rust economy cutover (R1/E1):
before Rust economy authority is enabled, every row's Target must be reached
and no Python (proxied) row may still write Core-owned state.
Cluster state = coins, owned inventory (items/purchased), unopened pack
entitlements (unopenedPackIds). points has no writer (read-only). EASFC
powFunds is a separate balance, out of cluster.
Legend: R = Rust/Core authoritative, P = Python proxied (oracle).
Evidence lines refer to fifa17-recon/tools/{utas_server.py,fut_store.py}.
Writer routes (mutate cluster state)
| Route | Method | Python handler | Writes | Current | Target | Core primitive |
|---|---|---|---|---|---|---|
/ut/game/<sku>/match |
POST | match_route→record_match (fut_store 554) |
coins | P | R | grant_reward |
/store/transaction |
PUT | store_buy→open_pack→spend (utas 3702) |
coins, purchased, packsOpened, nextItemId | P | R | purchase_entitlement (+ redeem_entitlement) |
/purchased |
POST | purchased_items→open_pack+consume_unopened_pack+move_items (utas 3716) |
coins, purchased, unopenedPackIds, items, nextItemId | P | R | redeem_entitlement |
/ut/game/<sku>/item/<id> |
DELETE | quick_sell_url_route→quick_sell (utas 1234) |
coins, items, purchased | P | R | sell_item |
/ut/delete/game/<sku>/item |
POST | quick_sell_route→quick_sell (utas 1273) |
coins, items, purchased | P | R | sell_item |
/ut/game/<sku>/item |
PUT | item_route→move_items (utas 1342) |
items, purchased | P | R | redeem_entitlement/move (inventory-only) |
/ut/game/<sku>/trade/<id> |
POST/PUT | trade_route buy-now spend+add_items (utas 3895/3899) |
coins, items, nextItemId | P | R | purchase_item (synthetic-seller mint) |
/auctionhouse,/transfermarket |
POST | auctionhouse_route→list_for_sale (utas 3865) |
listings, nextListingSeq | P | R | listing-state (see note) |
/ut/delete/game/<sku>/trade/<id> |
DELETE | delete_trade_route→remove_listing (utas 3935) |
listings | P | R | listing-state (see note) |
/ut/game/<sku>/squad |
PUT | squad_route→save_squad (utas 3430) |
squads (item refs) | R (SquadReplace→Core) | R | Core squad tx (already migrated) |
Note (market listings): listings/nextListingSeq are the user's own sale pile;
the buyable auction inventory is synthetic (PACK_POOL-derived, not persisted).
There is no sale-credit, expiry-return, or fee (audit §5). Listing/cancel move
no coins and no ownership, so they are low-risk; a minimal durable listing store
(or keeping the synthetic-only model) is the market slice's only decision.
Reader routes (emit cluster state; go STALE if Rust writes while these read Python)
| Route | Method | Python handler | Reads | Current | Target |
|---|---|---|---|---|---|
/user/credits |
GET | credits_route (utas 3765) |
coins, unopenedPackIds count | P | R (balance + entitlement count) |
/userMassInfo |
GET | massinfo currencies (utas 578) |
coins, points, record, items, unopenedPackIds, squad | P (.squad overlaid R) |
R economy fields (coins/packs), squad already R |
/store/purchasegroup |
GET | store_catalog→unopened_packs (utas 3614) |
unopenedPackIds | P + R topology overlay | R full-gen (catalog + SessionStore mode + Core entitlements) |
/tradePile |
GET | tradepile_route (utas 3911) |
items, listings, coins | P | R (reads Core inventory/balance/listings) |
/hub,/tradePile/counts,/watchList |
GET | hub_data/auction_counts/watchlist |
items, listings, coins | P | R |
/club,/club/stats,/user/list,/clubUser |
GET | club readers→items |
items | R (/club Core-backed) / P others |
R |
Writer → Core primitive map (Phase 2)
| Python writer | Reachable | Core primitive (services::economy) |
|---|---|---|
spend (pack buy leg) |
YES | purchase_entitlement debit leg |
open_pack |
YES | purchase_entitlement + redeem_entitlement (buy→entitlement→open split) |
consume_unopened_pack |
YES | redeem_entitlement (consume-once) |
move_items (purchased→club) |
YES | inventory add within redeem_entitlement / move op |
add_items (market mint) |
YES | purchase_item (debit + mint) |
quick_sell |
YES | sell_item (remove + credit) |
record_match (coins) |
YES | grant_reward (credit) |
new_item_id |
YES | adapter numeric-id via openfut-identity (Core ids opaque) |
list_for_sale / remove_listing |
YES | listing-state (market slice) |
grant_coins |
NO (dead) | — drop |
grant_unopened_pack |
NO (test-only) | — drop |
save_squad |
YES | already Core-authoritative (SquadReplace) |
Single-writer rule
Coins live only in Python fut_profile.json today (Core clubs.coins is a
separate imported value). Because every coin reader (credits, userMassInfo,
tradePile, market bodies) reads that same JSON, the coins cluster must flip
readers and writers together — a partial flip desyncs the client's counter
(audit "STALENESS RISK"). The coherent first cut is therefore the whole coins
bundle: 4 writer routes + credits/userMassInfo/purchasegroup readers, all
on Core, seeded by a one-time profile import into Core.
Proxied-route safety (R1 requirement: no unsafe YES)
After cutover, every remaining Python (proxied) route MUST have
economy state touched = NONE. Routes with economy state touched != NONE are
part of the migration cluster and MUST be Rust before R1. This table is the
audit source; the host classify() is the enforcement point (NEVER BOTH).
Cutover progress (2026-08-13)
Landed (Core authority + transport + first reader; classifier NOT yet flipped):
- Core economy HTTP API (
d32dc6e): generic/economy/{balance,entitlements, purchase-entitlement,redeem-entitlement,sell-item,grant-reward,purchase-item}, server-side club resolution, atomic transactions. Core matrix green. - Host
CoreEconomyclient (d240a61): typed reqwest transport, fail-closed, no Python fallback by contract. /user/creditshandler (handle_credits): Core-backed, oracle-shape body, 503 fail-closed. Tested viaFakeEconomy.
Not flipped: classify() still routes every economy route to Python. Per the
single-writer rule the flip is one coherent barrier once ALL writers+readers are
implemented and Core is seeded — a partial flip would desync coins. Remaining
handlers: purchasegroup full-gen, userMassInfo economy, Store BUY, pack-open,
quick-sell, market buy/list/cancel, match reward; then the barrier flip +
differential/concurrency/restart + no-fallback classifier tests.