fix(fifa17): map market resource ids to authoritative Core card ids

Closes the market correctness gap: handle_market_list recorded listing.card_id
from the raw FIFA wire resourceId, so a synthetic buy minted a card_id Core
could not resolve — it survived the immediate response but Core's content
preflight rejected it on reboot.

- catalog.rs: keep the by_resource reverse index (was built then discarded) and
  expose `card_id_for_resource(resource_id) -> Option<&str>` — exact reverse of
  the card_id->asset catalog, no heuristics, unknown => None.
- lib.rs: `impl MarketCardResolver for Fifa17IdentityResolver` delegates to the
  same catalog /club shaping uses; Core never sees a FIFA resource id.
- market_store.rs: listings now carry BOTH `card_id` (authoritative Core content,
  what a buy MINTS) and `wire_resource_id` (the FIFA wire id, echoed in the
  auction record). New column; create_listing takes both; row/Listing updated.
- market.rs: `MarketCardResolver` trait; handle_market_list resolves resourceId
  -> Core card_id and fails closed (persists nothing) on an unmappable resource;
  auction_record emits `resourceId` from wire_resource_id. Dispatch passes the
  resolver.

Tests: list_unknown_resource_fails_closed_no_listing (B),
list_persists_core_card_and_wire_resource_across_reopen (C), catalog reverse
lookup; and the dispatch E2E now RESTORES the full Core+store restart
(economy_full_sequence_through_dispatch_and_restart) — the synthetic buy mints a
real reverse-mapped card_id, so Core's content preflight passes on reboot (A+D).
market 23 lib + catalog 15 + 2 integration green; clippy -D warnings + fmt clean.
This commit is contained in:
OpenFUT Agent
2026-08-13 21:43:48 +00:00
parent 884ecbba64
commit fe72f0def2
5 changed files with 217 additions and 51 deletions
+25 -4
View File
@@ -863,6 +863,17 @@ impl SquadWireResolver for Fifa17IdentityResolver {
}
}
/// Reverse a FIFA wire `resourceId` to the authoritative Core `card_id`, via the
/// same catalog `/club` shaping uses — so a synthetic market buy mints real Core
/// content, never the raw FIFA number. Out-of-range or unmapped → `None`
/// (fail closed; Core never sees a FIFA resource id).
impl crate::market::MarketCardResolver for Fifa17IdentityResolver {
fn card_id_for_resource(&self, resource_id: i64) -> Option<String> {
let rid = u32::try_from(resource_id).ok()?;
self.catalog.card_id_for_resource(rid).map(str::to_string)
}
}
// ───────────────────────────── /club handler ────────────────────────────────
/// Safe, structured summary of a handled `/club` request (no auth/session/device
@@ -1914,12 +1925,22 @@ impl Server {
})
}
EconomyRoute::MarketList => {
let (bridge, market, econ) =
(svc.bridge.clone(), svc.market.clone(), svc.econ.clone());
let (bridge, market, econ, resolver) = (
svc.bridge.clone(),
svc.market.clone(),
svc.econ.clone(),
self.resolver.clone(),
);
let (m, body) = (method.to_string(), body.to_vec());
bridge.block_on(async move {
crate::market::handle_market_list(&m, &body, econ.as_ref(), market.as_ref())
.await
crate::market::handle_market_list(
&m,
&body,
econ.as_ref(),
market.as_ref(),
resolver.as_ref(),
)
.await
})
}
EconomyRoute::MarketQuery => {