docs(fifa17): numeric squad ids are real; our collapsing is safe, not authentic

The numeric id in squad/<n> was being justified as "matching the oracle". That
justification does not survive inspection, and the code now says why.

FIFA 17 has genuine multi-squad semantics. The client's own shipped action
table has SelectSquadById, RetrieveSquad as an action DISTINCT from
LoadActiveSquad, CreateSquadWithName, RenameSquad, DeleteSquad, CopySquad,
indexed SQUAD_ID-%d list entries and FUT_MAX_NUM_SQUAD_REACHED. The base
template is `ut/%s/squad` with the id appended. The number identifies a squad.

The inherited behaviour came from a bare prefix regex in the Python oracle -
`re.compile(G + r"/squad")` calling squad_route(), which never reads the URL id
(GET returns current_squad(), PUT echoes the id from the BODY). The comments
around it show /squad/list and the draft routes had to be registered first
because that rule was swallowing them. It was expedient, not evidence-driven.

Collapsing the id is nonetheless SAFE today, and only for a specific reason:
we advertise exactly one squad. ACTIVE_SQUAD_WIRE_ID is a constant 0,
/squad/list returns a single-element array carrying it, and no
create/rename/delete/copy route exists, so the client can only echo back the id
we gave it. Every numeric path in retained captures is squad/0, all PUTs whose
body id also reads 0; no numeric GET has ever been recorded.

Behaviour is therefore UNCHANGED - no evidence justifies changing it, and
unknown-id semantics are deliberately not invented. What changes is that the
assumption is now explicit and enforced:
numeric_squad_routing_is_safe_only_while_one_squad_is_advertised pins the wire
id at 0 and /squad/list at one entry, and fails the moment a second squad
becomes addressable. Verified by simulating a second advertised squad.

Workspace 1252 passed (1251 + this test), 0 failed.
This commit is contained in:
funman300
2026-08-24 22:35:28 +00:00
parent e49c1f211c
commit a2b0c32a70
2 changed files with 76 additions and 7 deletions
+31 -5
View File
@@ -206,8 +206,8 @@ pub enum Route {
/// Classify a request ONCE, before execution. Rust owns complete route families;
/// there is no "try Rust then Python", so a mutation can never be double-applied.
/// Numeric `GET …/squad/<n>` follows the oracle's single-current-squad behavior:
/// every numeric id returns the one Core-backed active squad.
/// Numeric `…/squad/<n>` resolves to the one Core-backed squad. That is SAFE
/// rather than authentic — see [`is_numeric_squad_tail`].
pub fn classify(method: &str, path: &str) -> Route {
let get = method.eq_ignore_ascii_case("GET");
let put = method.eq_ignore_ascii_case("PUT");
@@ -403,9 +403,35 @@ fn is_exact_club_path(path: &str) -> bool {
ut_tail(path) == Some("club")
}
/// `squad/<digits>` — the numeric full-squad target used by PUT and GET. Core
/// stores one current squad, matching the oracle: every numeric GET returns that
/// same squad regardless of the requested id.
/// `squad/<digits>` — the numeric full-squad target used by PUT and GET.
///
/// Every numeric id resolves to the one Core-backed squad. Be precise about why
/// that is acceptable: it is SAFE, not authentic.
///
/// FIFA 17 genuinely has multi-squad semantics. The client ships
/// `SelectSquadById`, `RetrieveSquad` as an action DISTINCT from
/// `LoadActiveSquad`, plus `CreateSquadWithName`, `RenameSquad`, `DeleteSquad`,
/// `CopySquad`, indexed `SQUAD_ID-%d` list entries and a
/// `FUT_MAX_NUM_SQUAD_REACHED` string. The number is therefore a real squad
/// identifier, not a placeholder.
///
/// It is unreachable here because we advertise exactly ONE squad:
/// [`ACTIVE_SQUAD_WIRE_ID`] is a constant `0`, `/squad/list` returns a
/// single-element array carrying that id, and no create/rename/delete/copy
/// route exists. The client can only ever echo back the id we gave it — every
/// numeric path observed in retained captures is `squad/0`, all of them PUTs
/// whose body `id` also reads 0, and no numeric GET has ever been recorded.
///
/// So collapsing the id costs nothing TODAY and is pinned by
/// `numeric_squad_routing_is_safe_only_while_one_squad_is_advertised`. The
/// moment a second squad becomes addressable, that test must fail and this
/// routing must gain a real lookup. Do NOT widen squad support without
/// revisiting it.
///
/// Unknown-id behaviour is deliberately NOT invented: no FIFA 17 evidence shows
/// what the client expects for an id it was never given. (The FIFA 14 oracle
/// Impulsum14 returns an empty squad carrying that id, never the active one —
/// hypothesis only, not FIFA 17 truth.)
fn is_numeric_squad_tail(tail: &str) -> bool {
match tail.strip_prefix("squad/") {
Some(id) => !id.is_empty() && id.bytes().all(|b| b.is_ascii_digit()),
+45 -2
View File
@@ -1111,8 +1111,9 @@ fn classify_squad_and_usermassinfo_routes() {
Route::Passthrough
);
// GET /squad/active and every numeric GET are the one Core-backed current
// squad, matching the oracle's single-squad response regardless of URL id.
// GET /squad/active and every numeric GET resolve to the one Core-backed
// squad. SAFE, not authentic — see is_numeric_squad_tail and the invariant
// test below.
assert_eq!(
classify("GET", "/ut/game/fifa17/squad/active"),
Route::SquadActive
@@ -2825,3 +2826,45 @@ fn no_shipped_training_card_exceeds_the_declared_ceiling() {
}
assert_eq!(rare, 6, "all 6 rare all-six cards must resolve");
}
/// Collapsing every numeric `squad/<id>` onto one squad is safe ONLY while
/// exactly one squad is advertised. This is the tripwire for that assumption.
///
/// FIFA 17 has real multi-squad semantics — the client ships `SelectSquadById`,
/// `RetrieveSquad` (distinct from `LoadActiveSquad`), `CreateSquadWithName`,
/// `RenameSquad`, `DeleteSquad` and indexed `SQUAD_ID-%d` entries. We get away
/// with ignoring the id purely because the client can never learn another one:
/// the wire id is a constant 0 and `/squad/list` advertises a single squad.
///
/// If either fact stops holding, the numeric route needs a real lookup and this
/// test must be the thing that says so.
#[test]
fn numeric_squad_routing_is_safe_only_while_one_squad_is_advertised() {
assert_eq!(
openfut_utas_host::ACTIVE_SQUAD_WIRE_ID,
0,
"the single advertised squad id is what makes id-collapsing safe"
);
let projected = serde_json::json!({
"id": openfut_utas_host::ACTIVE_SQUAD_WIRE_ID,
"squadName": "OpenFUT",
"formation": "f442",
"squadType": "REGULAR_SQUAD",
"rating": 90,
"chemistry": 52,
});
let list = openfut_adapter_fifa17::fut::squad_projection::squad_list(&projected);
let squads = list["squad"].as_array().expect("squad list is an array");
assert_eq!(
squads.len(),
1,
"more than one advertised squad means the client can address a second \
id, and every numeric GET/PUT collapsing onto one squad becomes wrong"
);
assert_eq!(
squads[0]["id"],
openfut_utas_host::ACTIVE_SQUAD_WIRE_ID,
"the advertised id must be the one the client echoes back"
);
}