85761390a8
Extend the UTAS migration host to own the squad slice, reusing the exact
production identity path (Fifa17CardCatalog + ExternalIdentityStore) that
/club uses, so /club, /squad/list, userMassInfo and PUT all agree on
wire<->owned identity.
Routing (classified ONCE, no try-Rust-then-Python):
PUT …/squad/<n> -> Rust (numeric id; …/squad/active stays Python)
GET …/squad/list -> Rust
GET …/userMassInfo-> Python proxy, ONLY .squad overlaid
everything else -> Python verbatim
CoreAccess gains read_squad_ext / replace_squad / all_owned (HTTP to the new
Core /squad/ext + /squad/replace routes).
PUT pipeline: parse -> build_squad_write (reverse-resolve every wire id;
refuse unresolved/duplicate) -> AUTHORIZE every resolved owned item against
the active club (identity resolution is NOT authorization; a valid wire id
owned by another profile is rejected before any mutation) -> Core atomic
replace+extension -> exactly {"id":0}. No Python fallback on failure; no
host-side second extension store; no fingerprint recomputation.
Read path assembles ONE projection input (one read_squad_ext + one batch
all_owned; no per-slot lookup) and runs the single adapter projector. Both
/squad/list and userMassInfo.squad derive from it. Fresh projects; Stale is
never applied; Missing is never fabricated — both are prominent integrity
failures, never served from Python (no split authority). The overlay
replaces only .squad and preserves userInfo/settings/userData/
pileSizeClientData, fixing Content-Length.
Tests: routing, full-replacement + exact ack, unknown/foreign/duplicate item
rejection with Core unchanged, idempotent repeat PUT, coupled read-after-
write (list + userMassInfo agree, real resourceIds + stable wire ids),
overlay field preservation, bounded no-N+1 reads, Stale/Missing integrity.