8819cc76a1
CI / Build, lint & test (push) Successful in 3m24s
PUT /club/manager took `owned_card_id: Option<String>`, so serde collapsed
"field absent" and "field explicitly null" into the same None, and the route
treated both as a clear. A caller that simply had nothing to say about the
manager therefore DELETED the assignment.
That is the second destructive squad-save path. WAL forensics on the staging
DB pin it to commit frame 468, squad_managers 1 row -> 0, in a transaction
touching only squad_managers and its indexes - disjoint from the player wipe
at frame 465, which touched squads/squad_players/game_entity_ext. The two
wipes came from two different writes, and only the first was guarded.
The three states are now distinct:
{} leave the manager exactly as it is
{"owned_card_id": null} explicitly remove it (still supported)
{"owned_card_id": "<id>"} assign that owned card
A deliberate removal is a legitimate operation and is preserved; only the
"absent means delete" reading is gone.
set_squad_manager_for_squad now runs its two existence checks and the insert
in ONE transaction. Validating on the pool and then inserting left a window in
which the squad or the card could disappear between check and write.
Tests cover assign, reassign, idempotent re-assign, absent-is-a-no-op,
explicit-null-still-removes, unowned-manager-refused, absent-against-no-manager
not over-guarded, and that no manager write disturbs player assignments. A
malformed body is asserted to be a parser rejection, distinguishable from the
guard. With the fix reverted the absent-field test fails.