Files
OpenFUT/openfut-utas-host
funman300 9cc5188e5c host: claim GET ut/%s/item (FutViewCards), which fell through to Python
Fifth instance of this project's recurring dead-route defect: a handler exists and
is correct, but classify() never produces the route, so every request falls
through to the Python upstream. Invisible in production, where the oracle answers;
on staging, where the upstream is deliberately dead, it is a 502.

GET ut/%s/item is FutViewCards (deser 0x1801293d0, top-level itemData via the
shared card element 0x18013fe00). The oracle answers it with defs_route - the SAME
handler already wired for item/resource and defid: pull every integer out of the
query (idList=a,b,c / definitionId= / resourceId=) and return one definition per
id, or {"itemData": []} when there are none (tools/utas_server.py:1103). So
claiming it is byte-identical parity, not new behaviour.

The query handling is the substance of the fix, not decoration. ut_tail does NOT
strip the query string, so an equality-only arm (`Some("item")`) misses every real
request while passing a no-query unit test - which is precisely how the route
stayed unclaimed. The same latent bug applied to the two arms that were already
there: item/resource and defid only matched with no query, so
`item/resource?resourceId=` and `defid?definitionId=` were ALSO falling through.
All three now mirror the oracle's own `item(\?|$)` pattern.

Verified on staging, all 200 where they were 502:
  GET /item                                  -> itemData 0
  GET /item?idList=100000003,100000004       -> itemData 2
  GET /ut/v2/game/fifa17/item                -> itemData 0
  GET /item/resource?resourceId=5003012      -> itemData 1
  GET /defid?definitionId=200389             -> itemData 1

Does NOT by itself fix the kit selector - GET /item is a definition lookup keyed
by ids the client already holds, not the thing that seeds the club collection, and
the observed session never requested it. It is a real production-masked gap and a
prerequisite for testing anything else on staging.

get_item_is_claimed_and_the_other_item_verbs_are_unaffected pins the claim plus
the three verbs that share the prefix: PUT item stays FutMoveCard on the economy
path and DELETE item/<id> stays QuickSellPath.

cargo test -p openfut-utas-host: 190 passed, 0 failed. clippy -D warnings clean.
2026-08-23 20:22:54 +00:00
..

openfut-utas-host

The FIFA 17 UTAS migration boundary. It accepts the client-visible HTTP surface, serves migrated routes from Rust/Core plus host-owned durable stores, and proxies only the unclassified tail to the Python behavioral oracle.

FIFA 17 ──HTTP──▶ openfut-utas-host
                    ├── migrated route ──▶ Rust adapter / Core / host stores
                    └── unclassified tail ──▶ Python UTAS oracle

src/lib.rs::classify is the route-level source of truth. The current Rust surface includes club/squad/user reads, club rename, auth/session/client data, Store/economy, packs, owned-item moves, market/trade-pile, and the observed hub support routes.

Safety model

  • Classification happens exactly once before execution. There is no "try Rust then Python"; a mutation cannot be double-applied.
  • A route classified to Rust never falls back to Python on a Core/store/projection failure. Each handler uses its captured fail-closed or honest-empty wire contract.
  • PUT …/club and PUT|POST …/user/club atomically update the shared account JSON. Every input returns the required zero-atom 200 {} response; rejection and persistence failures remain visible in logs.
  • Numeric GET …/squad/<n> returns the one Core-backed current squad, matching the Python oracle's single-current-squad behavior.
  • Python remains the behavioral oracle and rollback backend for routes not yet classified to Rust. New economy behavior belongs in Rust/Core, never Python.

Configuration (env)

Var Required Default Meaning
OPENFUT_UTAS_HOST_ADDR yes client-visible host listen address
OPENFUT_UTAS_PYTHON_URL yes Python oracle base for the unclassified tail; must differ from this host
OPENFUT_FIFA17_CATALOG yes FIFA 17 definition identity catalog
OPENFUT_IDENTITY_STORE yes persistent owned-instance ↔ wire-id store
OPENFUT_PERSONA_ID yes non-zero FIFA persona id shared by LSX/Blaze/POW/UTAS
OPENFUT_MARKET_DB yes durable host-owned transfer-market SQLite DB
OPENFUT_PILE_DB yes durable host-owned item-pile SQLite DB
OPENFUT_CORE_URL no http://127.0.0.1:8080 OpenFUT Core base
OPENFUT_FIFA17_TABLES_DIR no fifa17-recon/data/tables FIFA entity tables
OPENFUT_CLIENTDATA_DB no identity-store sibling clientdata.json durable opaque client-data JSON
OPENFUT_ACCOUNT_PATH no FUT_ACCOUNT_PATH, then identity-store sibling active_account.json shared FIFA account/club JSON

Startup fails if required identity or durable economy state cannot be opened. No placeholder production identity source is substituted.

Identity model (resolved)

FIFA renders an owned card by resolving resourceId & 0xffffff against the client's own local players table; an invented id renders a blank generic card (proven live — fut_cards.py:11-21). Two distinct identities, never conflated, are resolved by [Fifa17IdentityResolver] (the single production path):

  • Definition identity (resourceId/assetId) — the card's real FIFA asset id, from the versioned OPENFUT_FIFA17_CATALOG. An unmapped definition is dropped and counted, never faked.
  • Instance identity (id) — a stable, persistent, reversible wire integer from the generic openfut-identity store under the FIFA 17 wire-id policy (monotonic from 100_000_001). The same owned instance keeps its id across restart and reverses exactly; two copies of one definition share a resourceId but get distinct ids. The namespace is globally monotonic within (fifa17, owned-item) — no per-account column is needed because Core owned-instance ids are globally-unique UUIDs.

Production has a frozen post-P1 baseline and a hot Python rollback. A source change passing local tests is not deployment approval. Build verification, staging, host restart, and live-client promotion remain operator-gated; the current state and promotion evidence live in the OpenFUT Obsidian vault.

Logs are safe by construction: no auth/session/device/token material — only owner, route, filter summary, counts, status.